Documentation Index
Fetch the complete documentation index at: https://superdoc-nick-sd-2070-add-content-controls-namespace-to-doc.mintlify.app/llms.txt
Use this file to discover all available pages before exploring further.
Hocuspocus is TipTap’s open-source Yjs WebSocket server. It’s a mature, battle-tested option for self-hosted collaboration.
Setup
Server
npm install @hocuspocus/server
import { Server } from "@hocuspocus/server";
const server = Server.configure({
port: 1234,
async onLoadDocument(data) {
// Load document from database
const state = await db.getDocument(data.documentName);
if (state) {
Y.applyUpdate(data.document, state);
}
return data.document;
},
async onStoreDocument(data) {
// Save document to database
const state = Y.encodeStateAsUpdate(data.document);
await db.saveDocument(data.documentName, state);
},
async onAuthenticate(data) {
// Validate token
const user = await validateToken(data.token);
if (!user) {
throw new Error("Unauthorized");
}
return { user };
},
});
server.listen();
Client
Use the provider-agnostic API to connect SuperDoc:
npm install @hocuspocus/provider yjs
import { HocuspocusProvider } from "@hocuspocus/provider";
import * as Y from "yjs";
import { SuperDoc } from "superdoc";
const ydoc = new Y.Doc();
const provider = new HocuspocusProvider({
url: "ws://localhost:1234",
name: "document-123",
document: ydoc,
token: "auth-token", // Optional
});
// Wait for sync before creating editor
provider.on("synced", () => {
const superdoc = new SuperDoc({
selector: "#editor",
documentMode: "editing",
user: {
name: "John Smith",
email: "john@example.com",
},
modules: {
collaboration: { ydoc, provider },
},
});
});
SuperDoc JS always uses the same collaboration contract, regardless of provider: modules.collaboration = { ydoc, provider }.
React example
import { useEffect, useRef, useState } from "react";
import { HocuspocusProvider } from "@hocuspocus/provider";
import * as Y from "yjs";
import { SuperDoc } from "superdoc";
import "superdoc/style.css";
export default function Editor() {
const superdocRef = useRef<SuperDoc | null>(null);
const [users, setUsers] = useState<any[]>([]);
useEffect(() => {
const ydoc = new Y.Doc();
const provider = new HocuspocusProvider({
url: "ws://localhost:1234",
name: "my-document",
document: ydoc,
});
provider.on("synced", () => {
superdocRef.current = new SuperDoc({
selector: "#superdoc",
documentMode: "editing",
user: {
name: `User ${Math.floor(Math.random() * 1000)}`,
email: "user@example.com",
},
modules: {
collaboration: { ydoc, provider },
},
onAwarenessUpdate: ({ states }) => {
setUsers(states.filter((s) => s.user));
},
});
});
return () => {
superdocRef.current?.destroy();
provider.destroy();
};
}, []);
return (
<div>
<div className="users">
{users.map((u, i) => (
<span key={i} style={{ background: u.user?.color }}>
{u.user?.name}
</span>
))}
</div>
<div id="superdoc" style={{ height: "100vh" }} />
</div>
);
}
Server configuration
Basic options
Server.configure({
port: 1234,
timeout: 30000, // Connection timeout
debounce: 2000, // Debounce document saves
maxDebounce: 10000, // Max wait before save
quiet: false, // Disable logging
});
Hooks
| Hook | Purpose |
|---|
onLoadDocument | Load document from storage |
onStoreDocument | Save document to storage |
onAuthenticate | Validate user tokens |
onChange | React to document changes |
onConnect | Handle new connections |
onDisconnect | Handle disconnections |
Persistence example
import { Server } from "@hocuspocus/server";
import { Database } from "@hocuspocus/extension-database";
const server = Server.configure({
extensions: [
new Database({
fetch: async ({ documentName }) => {
const doc = await db.findOne({ name: documentName });
return doc?.data || null;
},
store: async ({ documentName, state }) => {
await db.upsert({ name: documentName }, { data: state });
},
}),
],
});
Provider options
const provider = new HocuspocusProvider({
url: "ws://localhost:1234",
name: "document-id",
document: ydoc,
// Optional
token: "auth-token",
awareness: awareness, // Custom awareness instance
connect: true, // Auto-connect on create
preserveConnection: true, // Keep connection on destroy
broadcast: true, // Broadcast changes to tabs
});
Events
Provider events
// Sync status
provider.on("synced", () => {
console.log("Document synced");
});
// Connection status
provider.on("status", ({ status }) => {
// 'connecting' | 'connected' | 'disconnected'
});
// Authentication
provider.on("authenticationFailed", ({ reason }) => {
console.error("Auth failed:", reason);
});
Production deployment
Docker
FROM node:18-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --production
COPY . .
EXPOSE 1234
CMD ["node", "server.js"]
With Redis (scaling)
import { Server } from "@hocuspocus/server";
import { Redis } from "@hocuspocus/extension-redis";
Server.configure({
extensions: [
new Redis({
host: "localhost",
port: 6379,
}),
],
});
Resources
Hocuspocus Docs
Official documentation
Working Example
Complete source code
Next steps
SuperDoc Yjs
Try the official collaboration package
Client Configuration
All SuperDoc collaboration options