Building a Distributed CRDT Sync Engine with Loro and WebSockets
Step-by-step architectural guide to building collaborative sync engines. Combine high-performance Loro CRDTs with WebSockets to handle network latency.

Step-by-step architectural guide to building collaborative sync engines. Combine high-performance Loro CRDTs with WebSockets to handle network latency.
Building a Distributed CRDT Sync Engine with Loro and WebSockets
Building collaborative applications (like Google Docs, Figma, or Miro) was once considered one of the hardest engineering tasks in software development. Managing concurrent, distributed updates from multiple users over unreliable, latent network connections is notoriously difficult.
If two users edit the same sentence at the exact same millisecond, how do you merge their keystrokes so that both screens show the identical final result without overwriting each other's work?
Historically, we relied on Operational Transform (OT)—the centralized algorithm powering Google Docs. OT is highly complex, requiring a single central coordinator server to receive, re-order, transform, and broadcast index changes.
In 2026, the modern web has moved decisively to CRDTs (Conflict-free Replicated Data Types). By structuring data mathematically so that updates can be applied in any order and naturally converge to the identical state, CRDTs enable truly decentralized, peer-to-peer, or lightweight server-coordinated collaboration.
And the new champion of high-performance CRDT libraries is Loro. Written in ultra-fast Rust with compiled WebAssembly browser bindings, Loro is significantly faster than legacy alternatives (like Yjs or Automerge).
In this architectural guide, we'll build a real-time collaborative text synchronization engine using Loro and a WebSockets connection.
⚡ 1. The Architecture: CRDT Convergence
Unlike OT (which requires a central ordering server), CRDTs store a rich mathematical history of every single edit, mapping each character to a unique client ID and a sequential counter (a Lamport timestamp).
When client A writes a character, Loro packs the operation into a highly compressed binary array containing only the mutation delta. This delta is sent over a WebSocket connection to Client B. When Client B applies the delta to their local Loro document state, the documents merge automatically.
[Client A Loro Doc] ──(binary delta update)──> [WebSocket Server]
│ │
[Converges to Same State] <──(binary delta update)─────┘ ──> [Client B Loro Doc]
Loro's internal Rust-optimized state engine ensures that no matter what network latencies occur or in what order updates are delivered, both clients mathematically converge to the exact same character sequence.
🏗️ 2. The Collaborative Client Implementation
Let's write our client-side collaboration script. Loro provides a compiled WebAssembly bundle.
typescriptimport { Loro } from "loro-crdt"; class CollaborativeEditor { private doc: Loro; private socket: WebSocket; private editorTextarea: HTMLTextAreaElement; private isApplyingRemoteUpdate = false; constructor(textarea: HTMLTextAreaElement, wsUrl: string) { this.editorTextarea = textarea; this.doc = new Loro(); this.socket = new WebSocket(wsUrl); this.initWebSocket(); this.initEditorListeners(); } private initWebSocket() { this.socket.binaryType = "arraybuffer"; this.socket.onmessage = (event: MessageEvent) => { const arrayBuffer = event.data; const updateBytes = new Uint8Array(arrayBuffer); console.log(`Received remote update: ${updateBytes.length} bytes`); // 1. Temporarily flag so we don't fire local change events back to socket this.isApplyingRemoteUpdate = true; // 2. Import the binary delta directly into our Loro doc this.doc.importUpdate(updateBytes); // 3. Render the newly converged text const richText = this.doc.getText("document-content"); this.editorTextarea.value = richText.toString(); this.isApplyingRemoteUpdate = false; }; } private initEditorListeners() { const richText = this.doc.getText("document-content"); this.editorTextarea.addEventListener("input", (event) => { if (this.isApplyingRemoteUpdate) return; const newText = this.editorTextarea.value; // 1. Let Loro calculate the semantic delta automatically this.doc.transact(() => { richText.delete(0, richText.toString().length); richText.insert(0, newText); }); // 2. Export only the binary mutation delta const updateBytes = this.doc.exportUpdate(); // 3. Broadcast the binary transaction over WebSockets if (this.socket.readyState === WebSocket.OPEN) { this.socket.send(updateBytes); } }); } }
🛠️ 3. The Lightweight Coordination Server (Node.js + ws)
Because Loro manages conflict resolution entirely on the client, our WebSocket server does not need to analyze SQL transactions or parse document indexes. It simply runs as a pub-sub message broadcaster:
javascript// Node.js coordination server using the 'ws' library import { WebSocketServer } from 'ws'; const wss = new WebSocketServer({ port: 8080 }); // Keep track of active client connections const clients = new Set(); wss.on('connection', (ws) => { clients.add(ws); console.log('Client connected. Active peers:', clients.size); // Set binary communication ws.binaryType = 'arraybuffer'; ws.on('message', (message, isBinary) => { if (!isBinary) return; // Broadcast the raw Loro binary delta update directly to all other clients for (const client of clients) { if (client !== ws && client.readyState === ws.OPEN) { client.send(message, { binary: true }); } } }); ws.on('close', () => { clients.delete(ws); console.log('Client disconnected. Active peers:', clients.size); }); });
🚀 4. Loro CRDT Advanced Capabilities: Time Travel
Because Loro keeps a rich mathematical graph of all document transactions, you get incredibly advanced features out of the box:
- Infinite Undos/Redos: Loro handles branching history internally, meaning undo/redo actions naturally resolve conflicts across users without writing complex UI undo stacks.
- Time Travel: You can request the document state at any historical timestamp or transaction counter.typescript
// View what the document looked like at transaction counter 105 const historicalDoc = this.doc.checkout(105); console.log(historicalDoc.getText("document-content").toString()); - Rich Text Styling: Loro supports rich text formatting attributes (bold, italic, links) directly inside its concurrent text node primitives, making it a perfect fit for building collaborative markdown or Wysiwyg editors.
🏁 5. Conclusion: Decoupling Collaborative Systems
The combination of Loro CRDTs and WebSockets completely shifts the engineering requirements of collaborative apps. By pushing mathematically guaranteed conflict resolution straight to compiled WebAssembly client threads, you bypass the need for expensive, complex centralized Operational Transform servers. Your backend remains a lightweight, serverless packet broadcaster, while your users experience ultra-responsive, zero-latency real-time collaboration.

Bun 1.2 vs. Node.js 22 vs. Deno 2.0: The Ultimate 2026 HTTP Throughput & Memory Benchmark
A rigorous, standardized developer-focused comparison of the three primary JavaScript runtimes of 2026, measuring raw throughput, memory leaks, and package manager overhead.

Postgres Row Level Security (RLS): Building Multi-tenant SaaS Backends Safely
Ditch manual tenant filters. Learn how to secure multi-tenant SaaS applications at the database level using Postgres Row Level Security (RLS) policies.