How to Implement Real-Time Collaboration Using Yjs with reactjs-tiptap-editor
Add the Yjs dependencies already present in the package, instantiate a HocuspocusProvider with a Y.Doc, configure the @tiptap/extension-collaboration and @tiptap/extension-collaboration-caret extensions, and wrap the editor in RichTextProvider to enable real-time synchronization.
The reactjs-tiptap-editor repository provides a complete rich-text editing solution built on Tiptap and ProseMirror. By leveraging the Conflict-free Replicated Data Type (CRDT) capabilities of Yjs, you can transform this standalone editor into a fully collaborative environment where multiple users edit the same document simultaneously with automatic conflict resolution.
Prerequisites and Dependencies
The repository already includes all necessary packages for real-time collaboration using Yjs in its package.json. You do not need to install additional dependencies:
{
"yjs": "^13.6.28",
"y-protocols": "^1.0.7",
"@tiptap/extension-collaboration": "^3.14.0",
"@tiptap/extension-collaboration-caret": "^3.14.0",
"@hocuspocus/provider": "^3.4.3"
}
Source: [package.json lines 887–889](https://github.com/hunghg255/reactjs-tiptap-editor/blob/main/package.json#L887-L889)
While @hocuspocus/provider is the default WebSocket transport, you can substitute it with y-webrtc or a custom Yjs provider without changing the extension configuration logic.
Setting Up the Yjs Document and Hocuspocus Provider
Create a Y.Doc instance and bind it to a provider before initializing the editor. In playground/src/App.tsx, import the required modules:
import * as Y from 'yjs';
import { HocuspocusProvider } from '@hocuspocus/provider';
import Collaboration from '@tiptap/extension-collaboration';
import CollaborationCaret from '@tiptap/extension-collaboration-caret';
Source: [playground/src/App.tsx lines 9–13](https://github.com/hunghg255/reactjs-tiptap-editor/blob/main/playground/src/App.tsx#L9-L13)
Instantiate the document and provider outside the component or in a stable scope:
const ydoc = new Y.Doc();
const provider = new HocuspocusProvider({
url: 'wss://your-hocuspocus-server.com',
name: 'document-room-id',
document: ydoc,
});
Source: [playground/src/App.tsx lines 99–105](https://github.com/hunghg255/reactjs-tiptap-editor/blob/main/playground/src/App.tsx#L99-L105)
The name parameter identifies the collaboration room, while document binds the provider to the specific Y.Doc instance that stores the shared document state.
Configuring the Collaboration Extensions
Add the collaboration extensions to your editor's extension array after the base kit. Pass the Y.Doc to Collaboration and the provider to CollaborationCaret:
const extensions = [
...BaseKit,
Collaboration.configure({
document: ydoc,
}),
CollaborationCaret.configure({
provider,
user: {
name: 'Current User',
color: '#ff6600',
},
}),
];
Source: Based on the commented block at [playground/src/App.tsx lines 332–340](https://github.com/hunghg255/reactjs-tiptap-editor/blob/main/playground/src/App.tsx#L332-L340)
Collaboration.configure() accepts the Y.Doc instance and replaces the default history extension with Yjs's native undo/redo stack. CollaborationCaret.configure() requires the provider to access awareness data, which tracks cursor positions and user selections across connected clients.
Managing Awareness and Cleanup
To monitor connected users, subscribe to the provider's awareness state:
provider.awareness.on('change', ({ added, updated, removed }) => {
const users = provider.awareness.getStates();
console.log('Active users:', users.size);
});
Prevent memory leaks by disconnecting the provider when the component unmounts:
useEffect(() => {
return () => provider.disconnect();
}, []);
The RichTextProvider component from reactjs-tiptap-editor handles the editor context for toolbars and bubble menus without requiring additional configuration for collaborative features.
Complete Working Implementation
Below is a minimal, production-ready example combining all steps:
// playground/src/App.tsx
import { useEffect } from 'react';
import { useEditor, EditorContent } from '@tiptap/react';
import { Document, Text, Paragraph } from '@tiptap/extensions';
import * as Y from 'yjs';
import { HocuspocusProvider } from '@hocuspocus/provider';
import Collaboration from '@tiptap/extension-collaboration';
import CollaborationCaret from '@tiptap/extension-collaboration-caret';
import { RichTextProvider } from 'reactjs-tiptap-editor';
import 'reactjs-tiptap-editor/style.css';
// 1. Initialize Yjs and Provider
const ydoc = new Y.Doc();
const provider = new HocuspocusProvider({
url: 'wss://your-hocuspocus-endpoint',
name: 'shared-doc-001',
document: ydoc,
});
// 2. Define base extensions
const BaseKit = [Document, Text, Paragraph];
export default function App() {
// 3. Create editor with collaboration
const editor = useEditor({
extensions: [
...BaseKit,
Collaboration.configure({ document: ydoc }),
CollaborationCaret.configure({
provider,
user: { name: 'User A', color: '#2563eb' },
}),
],
});
// 4. Cleanup on unmount
useEffect(() => {
return () => provider.disconnect();
}, []);
// 5. Render with RichTextProvider
return (
<RichTextProvider editor={editor}>
<EditorContent editor={editor} />
</RichTextProvider>
);
}
Open this component in multiple browser instances to observe real-time text synchronization and remote cursor visualization.
Key Implementation Files
Understanding these source files ensures proper integration:
package.json: Declares Yjs, Tiptap collaboration extensions, and the Hocuspocus provider dependencies.playground/src/App.tsx: Contains the commented reference implementation demonstrating provider instantiation and extension configuration.src/components/RichTextProvider.tsx: Wraps the editor instance to supply context for UI components; remains unchanged when adding collaboration.src/store/editor.ts: Provides theuseTiptapEditorhook consumed by the UI layer.
Best Practices for Production
Follow these guidelines when deploying real-time collaboration using Yjs:
- Unique Room Identification: Use distinct
namevalues (e.g.,doc-${uuid}) to isolate different documents. - Authentication: Pass auth tokens via
provider.setAuth({ token })before connecting to secure the WebSocket endpoint. - User Identity: Generate stable user IDs to prevent awareness data collisions; randomize colors per session for visual distinction.
- Garbage Collection: Enable
ydoc.gc = truefor long-running documents to prune deleted content and reduce memory usage. - Provider Alternatives: Substitute Hocuspocus with
y-webrtcfor peer-to-peer scenarios or a custom WebSocket server implementing the Yjs sync protocol.
Summary
- The
reactjs-tiptap-editorrepository includesyjs,@tiptap/extension-collaboration, and@hocuspocus/providerby default. - Instantiate a
Y.DocandHocuspocusProviderwith your WebSocket endpoint before creating the editor instance. - Configure
Collaborationwith theY.DocandCollaborationCaretwith the provider to enable synchronization and cursor awareness. - Wrap the editor in
RichTextProviderto maintain toolbar functionality; no internal changes to the provider are required. - Disconnect the provider in a cleanup effect to prevent memory leaks and orphaned WebSocket connections.
Frequently Asked Questions
What is the difference between the Collaboration and CollaborationCaret extensions?
@tiptap/extension-collaboration handles document state synchronization by binding the ProseMirror editor to a Y.Doc, ensuring all clients share the same content. @tiptap/extension-collaboration-caret provides the visual layer, reading awareness data from the provider to render remote users' cursors and selections with distinct colors.
Can I use a different provider instead of Hocuspocus?
Yes. Any Yjs-compatible provider works, including y-webrtc for peer-to-peer connections or a custom WebSocket server. Replace the HocuspocusProvider import and instantiation while keeping the same Y.Doc reference passed to Collaboration.configure().
How do I handle authentication for the collaboration server?
Configure authentication through the provider options before connecting. For Hocuspocus, use provider.setAuth({ token: 'your-jwt' }) or pass an onAuthentication callback in the constructor. Secure the WebSocket endpoint server-side to validate tokens before allowing document sync.
Why do I need to disable the default History extension when using collaboration?
The standard Tiptap History extension manages undo/redo through ProseMirror's native mechanism, which conflicts with Yjs's CRDT-based history. When you add Collaboration, it automatically replaces the native history with Yjs's shared undo manager, ensuring all clients maintain a consistent operation history across the collaborative session.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →