Get real-time collaboration working in 5 minutes using Liveblocks - no server required.
This guide uses Liveblocks because it’s the fastest way to get started. See all providers for alternatives.
Prerequisites
Step 1: Get your API key
- Go to liveblocks.io and create an account
- Create a new project
- Copy your Public API Key (starts with
pk_)
Step 2: Install dependencies
npm install @liveblocks/client @liveblocks/yjs yjs
Step 3: Add the code
import { useEffect, useRef } from 'react';
import { createClient } from '@liveblocks/client';
import { LiveblocksYjsProvider } from '@liveblocks/yjs';
import * as Y from 'yjs';
import { SuperDoc } from 'superdoc';
import 'superdoc/style.css';
const client = createClient({
publicApiKey: 'pk_your_public_key'
});
export default function Editor() {
const superdocRef = useRef<SuperDoc | null>(null);
useEffect(() => {
// Enter a room (document ID)
const { room, leave } = client.enterRoom('my-document');
// Create Yjs document and provider
const ydoc = new Y.Doc();
const provider = new LiveblocksYjsProvider(room, ydoc);
// Wait for sync before creating editor
provider.on('sync', (synced: boolean) => {
if (!synced) return;
superdocRef.current = new SuperDoc({
selector: '#superdoc',
documentMode: 'editing',
user: {
name: 'User ' + Math.floor(Math.random() * 1000),
email: 'user@example.com'
},
modules: {
collaboration: { ydoc, provider }
}
});
});
// Cleanup on unmount
return () => {
superdocRef.current?.destroy();
provider.destroy();
leave();
};
}, []);
return <div id="superdoc" style={{ height: '100vh' }} />;
}
<script setup lang="ts">
import { onMounted, onBeforeUnmount, shallowRef } from 'vue';
import { createClient } from '@liveblocks/client';
import { LiveblocksYjsProvider } from '@liveblocks/yjs';
import * as Y from 'yjs';
import { SuperDoc } from 'superdoc';
import 'superdoc/style.css';
const client = createClient({
publicApiKey: 'pk_your_public_key'
});
const superdoc = shallowRef<SuperDoc | null>(null);
let provider: LiveblocksYjsProvider | null = null;
let leave: (() => void) | null = null;
onMounted(() => {
const result = client.enterRoom('my-document');
leave = result.leave;
const ydoc = new Y.Doc();
provider = new LiveblocksYjsProvider(result.room, ydoc);
provider.on('sync', (synced: boolean) => {
if (!synced) return;
superdoc.value = new SuperDoc({
selector: '#superdoc',
documentMode: 'editing',
user: {
name: 'User ' + Math.floor(Math.random() * 1000),
email: 'user@example.com'
},
modules: {
collaboration: { ydoc, provider }
}
});
});
});
onBeforeUnmount(() => {
superdoc.value?.destroy();
provider?.destroy();
leave?.();
});
</script>
<template>
<div id="superdoc" style="height: 100vh" />
</template>
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="node_modules/superdoc/dist/style.css">
</head>
<body>
<div id="superdoc" style="height: 100vh"></div>
<script type="module">
import { createClient } from '@liveblocks/client';
import { LiveblocksYjsProvider } from '@liveblocks/yjs';
import * as Y from 'yjs';
import { SuperDoc } from 'superdoc';
const client = createClient({
publicApiKey: 'pk_your_public_key'
});
const { room } = client.enterRoom('my-document');
const ydoc = new Y.Doc();
const provider = new LiveblocksYjsProvider(room, ydoc);
provider.on('sync', (synced) => {
if (!synced) return;
new SuperDoc({
selector: '#superdoc',
documentMode: 'editing',
user: {
name: 'User ' + Math.floor(Math.random() * 1000),
email: 'user@example.com'
},
modules: {
collaboration: { ydoc, provider }
}
});
});
</script>
</body>
</html>
Step 4: Test it
- Open your app in two browser windows
- Start typing in one window
- Watch changes appear in real-time in the other window
You now have real-time collaboration working!
What’s happening?
- Liveblocks handles the WebSocket connection and data sync
- Yjs manages the document state and conflict resolution
- SuperDoc renders the editor and syncs with Yjs
Adding user presence
Show who’s currently editing:
const superdoc = new SuperDoc({
// ... other config
onAwarenessUpdate: ({ states }) => {
const users = states.filter(s => s.user);
console.log('Active users:', users);
// Update your UI
updateUserList(users.map(u => ({
name: u.user.name,
color: u.user.color
})));
}
});
Environment variables
For production, use environment variables:
const client = createClient({
publicApiKey: import.meta.env.VITE_LIVEBLOCKS_PUBLIC_KEY
// or process.env.NEXT_PUBLIC_LIVEBLOCKS_KEY for Next.js
});
Next steps