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.







