A dev team I know spent 3 months building real-time collaboration from scratch. WebSocket server, conflict resolution, presence tracking, undo/redo. Then they found Liveblocks — they replaced 8,000 lines of code with 200.
What Liveblocks Offers for Free
Liveblocks free tier:
- Up to 200 monthly active users
- Real-time presence — see who's online, cursor positions
- Conflict-free data — CRDT-based storage
- Room-based architecture — isolated collaborative spaces
- Comments — threaded discussions on any element
- Notifications — in-app notification system
- React, Vue, and vanilla JS support
Quick Start
npm install @liveblocks/client @liveblocks/react
// liveblocks.config.ts
import { createClient } from '@liveblocks/client';
import { createRoomContext } from '@liveblocks/react';
const client = createClient({
publicApiKey: 'pk_YOUR_PUBLIC_KEY'
});
export const { RoomProvider, useOthers, useMyPresence, useStorage, useMutation } = createRoomContext(client);
Real-Time Cursors
import { RoomProvider, useOthers, useMyPresence } from './liveblocks.config';
function App() {
return (
<RoomProvider id="my-room" initialPresence={{ cursor: null }}>
<Canvas />
</RoomProvider>
);
}
function Canvas() {
const others = useOthers();
const [myPresence, updateMyPresence] = useMyPresence();
return (
<div
style={{ width: '100vw', height: '100vh', position: 'relative' }}
onPointerMove={(e) => {
updateMyPresence({ cursor: { x: e.clientX, y: e.clientY } });
}}
onPointerLeave={() => updateMyPresence({ cursor: null })}
>
{others.map(({ connectionId, presence }) => {
if (!presence.cursor) return null;
return (
<div
key={connectionId}
style={{
position: 'absolute',
left: presence.cursor.x,
top: presence.cursor.y,
width: 8,
height: 8,
borderRadius: '50%',
background: `hsl(${connectionId * 50}, 70%, 50%)`
}}
/>
);
})}
</div>
);
}
Collaborative Storage (CRDT)
import { useMutation, useStorage } from './liveblocks.config';
function TodoList() {
const todos = useStorage((root) => root.todos);
const addTodo = useMutation(({ storage }, text) => {
storage.get('todos').push({ text, done: false, id: Date.now() });
}, []);
const toggleTodo = useMutation(({ storage }, index) => {
const todo = storage.get('todos').get(index);
todo.set('done', !todo.get('done'));
}, []);
return (
<div>
<button onClick={() => addTodo('New task')}>Add</button>
{todos?.map((todo, i) => (
<div key={todo.id} onClick={() => toggleTodo(i)}>
{todo.done ? '✅' : '⬜'} {todo.text}
</div>
))}
</div>
);
}
REST API
# Get active users in a room
curl 'https://api.liveblocks.io/v2/rooms/my-room/active_users' \
-H 'Authorization: Bearer sk_YOUR_SECRET_KEY'
# Get storage data
curl 'https://api.liveblocks.io/v2/rooms/my-room/storage' \
-H 'Authorization: Bearer sk_YOUR_SECRET_KEY'
# Initialize room storage
curl -X POST 'https://api.liveblocks.io/v2/rooms/my-room/storage' \
-H 'Authorization: Bearer sk_YOUR_SECRET_KEY' \
-H 'Content-Type: application/json' \
-d '{ "liveblocksType": "LiveObject", "data": { "todos": { "liveblocksType": "LiveList", "data": [] } } }'
Use Cases
- Collaborative editors — Notion-like docs, spreadsheets
- Design tools — Figma-like multiplayer canvas
- Whiteboards — Miro-like collaborative boards
- Project management — Real-time kanban boards
- Forms — Multi-user form filling
Need real-time data from the web? Check out my web scraping actors on Apify — automated data collection.
Building a collaborative app? Email me at spinov001@gmail.com.
Top comments (0)