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.
What this skill does
# Liveblocks — Real-Time Collaboration SDK
## 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:
```typescript
// 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:
```tsx
// 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:
```tsx
// 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:
```tsx
// 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:
```typescript
// 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
```bash
# 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 @liveblRelated in Web Dev
generating-lwc-components
IncludedLightning Web Components with PICKLES methodology and 165-point scoring. Use this skill when the user creates or edits LWC components, builds wire service patterns, or writes Jest tests for LWC. TRIGGER when: user creates/edits LWC components, touches lwc/**/*.js, .html, .css, .js-meta.xml files, or asks about wire service, SLDS, or Jest LWC tests. DO NOT TRIGGER when: Apex classes (use generating-apex), Aura components, or Visualforce.
tanstack-query
IncludedManage server state in React with TanStack Query v5. Set up queries with useQuery, mutations with useMutation, configure QueryClient caching strategies, implement optimistic updates, and handle infinite scroll with useInfiniteQuery. Use when: setting up data fetching in React projects, migrating from v4 to v5, or fixing object syntax required errors, query callbacks removed issues, cacheTime renamed to gcTime, isPending vs isLoading confusion, keepPreviousData removed problems.
document-processor-api
IncludedProcess documents with Nutrient DWS. Use when the user wants to generate PDFs from HTML or URLs, convert Office/images/PDFs, assemble or split packets, OCR scans, extract text/tables/key-value pairs, redact PII, watermark, sign, fill forms, optimize PDFs, or produce compliance outputs like PDF/A or PDF/UA. Triggers include convert to PDF, merge these PDFs, OCR this scan, extract tables, redact PII, sign this PDF, make this PDF/A, or linearize for web delivery.
nutrient-document-processing
IncludedProcess documents with Nutrient DWS. Use when the user wants to generate PDFs from HTML or URLs, convert Office/images/PDFs, assemble or split packets, OCR scans, extract text/tables/key-value pairs, redact PII, watermark, sign, fill forms, optimize PDFs, or produce compliance outputs like PDF/A or PDF/UA. Triggers include convert to PDF, merge these PDFs, OCR this scan, extract tables, redact PII, sign this PDF, make this PDF/A, or linearize for web delivery.
tanstack-query
IncludedManage server state in React with TanStack Query v5. Covers useMutationState, simplified optimistic updates, throwOnError, network mode (offline/PWA), and infiniteQueryOptions. Use when setting up data fetching, fixing v4→v5 migration errors (object syntax, gcTime, isPending, keepPreviousData), or debugging SSR/hydration issues with streaming server components.
accelint-nextjs-best-practices
IncludedNext.js performance optimization and best practices. Use when writing Next.js code (App Router or Pages Router); implementing Server Components, Server Actions, or API routes; optimizing RSC serialization, data fetching, or server-side rendering; reviewing Next.js code for performance issues; fixing authentication in Server Actions; or implementing Suspense boundaries, parallel data fetching, or request deduplication.