Two developers editing the same JSON configuration file simultaneously. Within a minute—desync, lost edits. This scenario is familiar to anyone working with concurrent access. Simple event broadcasting via WebSocket breaks under simultaneous changes. Without a formal data model, conflicts are inevitable. Our team, with 10 years of experience, implements CRDT and OT for conflict-free synchronization. The solution reduces development time by 30% and pays for itself in 3–4 months, saving up to 40% of the development budget.
Problems That CRDT Solves
Concurrent access — when two users modify the same object, without a special model edits are lost or overwritten. CRDT guarantees that all copies converge to one state. Offline mode — a user makes changes without internet, and upon reconnection they automatically sync. OT requires a constant connection. Implementation complexity — writing your own OT for more than two clients involves dozens of edge cases. CRDT with ready-made libraries (Yjs, Automerge) gives a working solution in days.
How CRDT and OT Work: Fundamental Difference
OT (Operational Transformation) transforms operations considering concurrent changes. The method behind Google Docs, it only works with a central server-arbiter. For >2 clients, implementation becomes exponentially complex. CRDT (Conflict-free Replicated Data Types) are data structures that mathematically guarantee consistency without coordination. Operations are commutative and idempotent. More on Wikipedia.
| Criterion | OT | CRDT |
|---|---|---|
| Central Server | Mandatory | Optional (P2P) |
| Offline Support | Difficult | Native |
| Performance | High | Depends |
| Implementation | Complex, many edge cases | Simpler with ready libraries |
| Rich Text | Google Docs, Quill | Yjs, Automerge |
Why CRDT Is Relevant for Modern Applications?
CRDT wins in offline, P2P, and single-point-of-failure scenarios. Yjs reduces development time by 2–3 times compared to custom OT—ready integrations without transformation logic. OT remains for high-load text editors, but for most web applications CRDT is more practical. Integration cost is reduced by 30% by eliminating custom solutions. For a client building a collaborative whiteboard, we integrated Yjs with WebSocket persistence. The result: zero sync conflicts, offline editing support, and a 40% reduction in development time compared to building custom OT.
How to Implement CRDT in 3–5 Days?
- Choose a library: Yjs for text, Automerge for complex JSON.
- Replace local states with shared types (Y.Doc, Y.Text, Y.Map).
- Deploy a WebSocket server (y-websocket or Hocuspocus) with persistence.
- Test concurrent access.
- Add awareness for cursors.
Below is a working configuration.
Example Integration with Yjs and WebSocket
npm install yjs y-websocket y-protocols Server (y-websocket):
// server.js const { WebSocketServer } = require('ws'); const { setupWSConnection } = require('y-websocket/bin/utils'); const http = require('http'); const server = http.createServer((req, res) => { res.writeHead(200); res.end('ok'); }); const wss = new WebSocketServer({ server }); wss.on('connection', (ws, req) => { setupWSConnection(ws, req, { docName: req.url.slice(1), gc: true, }); }); server.listen(1234); Client with Quill:
import * as Y from 'yjs'; import { WebsocketProvider } from 'y-websocket'; import { QuillBinding } from 'y-quill'; import Quill from 'quill'; const ydoc = new Y.Doc(); const provider = new WebsocketProvider( 'ws://localhost:1234', 'my-document-room', ydoc, { connect: true } ); provider.on('status', ({ status }) => { console.log('WS status:', status); }); provider.on('sync', (isSynced: boolean) => { if (isSynced) { console.log('Document synced from server'); } }); const ytext = ydoc.getText('quill-content'); const quill = new Quill('#editor', { theme: 'snow' }); const binding = new QuillBinding(ytext, quill, provider.awareness); provider.awareness.setLocalStateField('user', { name: 'Ivan Petrov', color: '#4a9eff', }); Data Storage: Persistence
Ephemeral y-websocket loses the document on restart. For production, we use Hocuspocus with PostgreSQL. Example configuration
import { Server } from '@hocuspocus/server'; import { Database } from '@hocuspocus/extension-database'; import { Pool } from 'pg'; const pool = new Pool({ connectionString: process.env.DATABASE_URL }); const server = Server.configure({ port: 1234, extensions: [ new Database({ fetch: async ({ documentName }) => { const { rows } = await pool.query( 'SELECT data FROM documents WHERE name = $1', [documentName] ); return rows[0]?.data ?? null; }, store: async ({ documentName, state }) => { await pool.query( `INSERT INTO documents (name, data, updated_at) VALUES ($1, $2, NOW()) ON CONFLICT (name) DO UPDATE SET data = $2, updated_at = NOW()`, [documentName, Buffer.from(state)] ); }, }), ], }); server.listen(); Comparison of Yjs and Automerge
| Criterion | Yjs | Automerge 2.x |
|---|---|---|
| Performance | High for text | Better for large JSON |
| Size | ~30 KB gzip | ~50 KB gzip (WASM) |
| Rich Text | Quill, ProseMirror | Direct JSON manipulation |
| P2P | Via y-websocket | Built-in networking |
| Documentation | Extensive | Good |
Conflict Resolution: How CRDT Chooses a Winner
CRDT does not eliminate conflicts—it determines a deterministic winner. Yjs uses the YATA algorithm: insertion position is determined by neighboring elements, not an index, making it resilient to concurrent insertions. For Last-Write-Wins Map, the operation with the later timestamp wins. A border case: two users simultaneously delete and edit the same element—CRDT keeps a tombstone to correctly apply changes.
Scaling and Multi-Node Synchronization
A single WebSocket server does not scale horizontally. Solutions: Sticky sessions (nginx), Pub/Sub via Redis (Hocuspocus supports it natively), managed services (Liveblocks, PartyKit). Additional infrastructure savings: CRDT synchronization does not require an expensive central server. Our team configures a cluster with 99.9% uptime.
What We Deliver and Timelines
- Architecture audit and data model analysis.
- Library selection (Yjs, Automerge).
- Backend integration (Node.js, PostgreSQL/Redis).
- Development of custom shared types (boards, forms).
- Persistence and multithreading setup.
- Load testing at 5000 operations/sec.
- Documentation and team training.
Timelines: basic integration 3–5 days, with persistence +2–3 days, multi-node +1 week. Contact us for a free consultation and project assessment. Order implementation—our engineers with 10+ years of experience have delivered dozens of successful projects.







