liveblocks
Expert guidance for Liveblocks, the platform for adding real-time collaboration features to web applications. Helps developers implement live cursors, presence indicators, collaborative editing, comments, and notifications using Liveblocks' React hooks and APIs.
Usage
Getting Started
- Install the skill using the command above
- Open your AI coding agent (Claude Code, Codex, Gemini CLI, or Cursor)
- Reference the skill in your prompt
- The AI will use the skill's capabilities automatically
Example Prompts
- "Review the open pull requests and summarize what needs attention"
- "Generate a changelog from the last 20 commits on the main branch"
Documentation
Overview
Liveblocks, the platform for adding real-time collaboration features to web applications. Helps developers implement live cursors, presence indicators, collaborative editing, comments, and notifications using Liveblocks' React hooks and APIs.
Instructions
Room Setup and Presence
Configure a collaborative room with user presence tracking:
// src/liveblocks.config.ts — Liveblocks type configuration
import { createClient } from "@liveblocks/client";
import { createRoomContext, createLiveblocksContext } from "@liveblocks/react";
const client = createClient({
publicApiKey: process.env.NEXT_PUBLIC_LIVEBLOCKS_PUBLIC_KEY!,
// Or use auth endpoint for production (recommended)
// authEndpoint: "/api/liveblocks-auth",
});
// Define types for your collaborative data
type Presence = {
cursor: { x: number; y: number } | null; // User's cursor position
selectedId: string | null; // Currently selected element
name: string; // Display name
color: string; // Avatar/cursor color
};
type Storage = {
shapes: LiveList<Shape>; // Shared canvas shapes
document: LiveObject<DocumentState>; // Shared document state
};
type UserMeta = {
id: string;
info: { name: string; avatar: string; color: string };
};
export const {
RoomProvider,
useMyPresence,
useOthers,
useStorage,
useMutation,
useSelf,
} = createRoomContext<Presence, Storage, UserMeta>(client);
Live Cursors
Show other users' cursor positions in real time:
// src/components/LiveCursors.tsx — Display cursors of all connected users
import { useOthers, useMyPresence } from "../liveblocks.config";
import { useCallback, useEffect } from "react";
export function LiveCursors() {
const others = useOthers();
const [myPresence, updateMyPresence] = useMyPresence();
// Track cursor movement and broadcast to other users
const handlePointerMove = useCallback(
(e: React.PointerEvent) => {
updateMyPresence({
cursor: { x: e.clientX, y: e.clientY },
});
},
[updateMyPresence]
);
const handlePointerLeave = useCallback(() => {
updateMyPresence({ cursor: null });
}, [updateMyPresence]);
return (
<div
onPointerMove={handlePointerMove}
onPointerLeave={handlePointerLeave}
style={{ position: "relative", width: "100%", height: "100vh" }}
>
{/* Render other users' cursors */}
{others.map(({ connectionId, presence, info }) => {
if (!presence.cursor) return null;
return (
<Cursor
key={connectionId}
x={presence.cursor.x}
y={presence.cursor.y}
name={info?.name ?? "Anonymous"}
color={info?.color ?? "#000"}
/>
);
})}
</div>
);
}
function Cursor({ x, y, name, color }: { x: number; y: number; name: string; color: string }) {
return (
<div style={{ position: "absolute", left: x, top: y, pointerEvents: "none" }}>
{/* SVG cursor icon */}
<svg width="24" height="24" viewBox="0 0 24 24" fill={color}>
<path d="M5 3l14 8-6 2-2 6z" />
</svg>
{/* Name label */}
<span style={{
backgroundColor: color,
color: "white",
padding: "2px 8px",
borderRadius: "4px",
fontSize: "12px",
whiteSpace: "nowrap",
}}>
{name}
</span>
</div>
);
}
Collaborative Storage
Shared data structures that sync across all users:
// src/components/CollaborativeCanvas.tsx — Shared canvas with conflict-free updates
import { useStorage, useMutation } from "../liveblocks.config";
import { LiveList, LiveObject } from "@liveblocks/client";
type Shape = {
id: string;
type: "rectangle" | "circle" | "text";
x: number;
y: number;
width: number;
height: number;
fill: string;
};
export function CollaborativeCanvas() {
// useStorage reads from the shared room storage (synced via CRDT)
const shapes = useStorage((root) => root.shapes);
// useMutation creates a function that can modify shared storage
// Mutations are atomic and conflict-free — two users can edit simultaneously
const addShape = useMutation(({ storage }, shape: Shape) => {
const shapes = storage.get("shapes");
shapes.push(new LiveObject(shape));
}, []);
const moveShape = useMutation(({ storage }, id: string, x: number, y: number) => {
const shapes = storage.get("shapes");
const shape = shapes.find((s) => s.get("id") === id);
if (shape) {
shape.set("x", x); // Only the changed field syncs — bandwidth efficient
shape.set("y", y);
}
}, []);
const deleteShape = useMutation(({ storage }, id: string) => {
const shapes = storage.get("shapes");
const index = shapes.findIndex((s) => s.get("id") === id);
if (index !== -1) shapes.delete(index);
}, []);
return (
<canvas>
{shapes?.map((shape) => (
<CanvasShape
key={shape.id}
shape={shape}
onMove={(x, y) => moveShape(shape.id, x, y)}
onDelete={() => deleteShape(shape.id)}
/>
))}
</canvas>
);
}
Comments and Threads
Add comment threads to any part of your application:
// src/components/Comments.tsx — Inline comments on document elements
import { useThreads, useCreateThread, useCreateComment } from "@liveblocks/react/suspense";
export function CommentsSidebar({ elementId }: { elementId: string }) {
// Fetch all comment threads for this room
const { threads } = useThreads();
const createThread = useCreateThread();
const createComment = useCreateComment();
// Filter threads attached to the selected element
const elementThreads = threads.filter(
(thread) => thread.metadata.elementId === elementId
);
const handleNewThread = (body: string) => {
createThread({
body: { version: 1, content: [{ type: "paragraph", children: [{ text: body }] }] },
metadata: { elementId, resolved: false },
});
};
return (
<div className="comments-sidebar">
<h3>Comments</h3>
{elementThreads.map((thread) => (
<ThreadView
key={thread.id}
thread={thread}
onReply={(body) => createComment({ threadId: thread.id, body })}
/>
))}
<NewThreadForm onSubmit={handleNewThread} />
</div>
);
}
Authentication Endpoint
Secure room access with token-based auth:
// app/api/liveblocks-auth/route.ts — Next.js auth endpoint
import { Liveblocks } from "@liveblocks/node";
import { NextRequest, NextResponse } from "next/server";
import { getSession } from "@/lib/auth";
const liveblocks = new Liveblocks({
secret: process.env.LIVEBLOCKS_SECRET_KEY!,
});
export async function POST(request: NextRequest) {
const session = await getSession();
if (!session?.user) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}
// Prepare Liveblocks session with user identity
const liveblocksSession = liveblocks.prepareSession(session.user.id, {
userInfo: {
name: session.user.name,
avatar: session.user.image,
color: generateColor(session.user.id), // Deterministic color from user ID
},
});
// Grant access to specific rooms based on your authorization logic
const { room } = await request.json();
const hasAccess = await checkRoomAccess(session.user.id, room);
if (hasAccess) {
liveblocksSession.allow(room, liveblocksSession.FULL_ACCESS);
}
const { status, body } = await liveblocksSession.authorize();
return new NextResponse(body, { status });
}
Installation
# Core packages
npm install @liveblocks/client @liveblocks/react
# For Next.js with comments and notifications
npm install @liveblocks/node @liveblocks/react-ui
# Yjs integration for text editing
npm install @liveblocks/yjs yjs
Examples
Example 1: Setting up Liveblocks with a custom configuration
User request:
I just installed Liveblocks. Help me configure it for my TypeScript + React workflow with my preferred keybindings.
The agent creates the configuration file with TypeScript-aware settings, configures relevant plugins/extensions for React development, sets up keyboard shortcuts matching the user's preferences, and verifies the setup works correctly.
Example 2: Extending Liveblocks with custom functionality
User request:
I want to add a custom live cursors to Liveblocks. How do I build one?
The agent scaffolds the extension/plugin project, implements the core functionality following Liveblocks's API patterns, adds configuration options, and provides testing instructions to verify it works end-to-end.
Guidelines
- Use auth endpoint in production — Public API keys are fine for dev; production needs proper authentication
- Type your presence and storage — TypeScript generics prevent runtime errors in collaborative data
- Granular mutations — Update individual fields, not entire objects; Liveblocks only syncs what changed
- Throttle presence updates — Cursor movement fires constantly; throttle to 50-100ms intervals
- Handle offline gracefully — Liveblocks queues changes offline; show a connection status indicator
- Room naming convention — Use predictable patterns like
project:{projectId}:document:{docId} - Clean up storage — Delete rooms when projects are archived; storage persists indefinitely
- Test with multiple tabs — Open your app in 2-3 tabs during development to verify real-time sync
Information
- Version
- 1.0.0
- Author
- terminal-skills
- Category
- Development
- License
- Apache-2.0