← /research

CRDTs for Real-Time Collaboration

Processing · Reading Notes Created Jan 4, 2025

Source

Loro CRDT Library — Loro Dev (Article)
View source →
Project: web-graphics-research
crdtcollaborationmultiplayerarchitecture

CRDTs (Conflict-free Replicated Data Types) enable real-time collaboration without a central authority resolving conflicts. Essential for local-first and multiplayer apps.

CRDT vs Operational Transform

AspectOTCRDT
ArchitectureRequires central serverWorks P2P
OfflineComplexNative support
ComplexityServer-side transformsClient-side, simpler
LatencyRound-trip to serverImmediate local

Figma’s approach: CRDT-inspired, but uses a central server for simplicity. Pure CRDTs work fully P2P.

Top Libraries (2025)

High-performance Rust CRDT library with JS/Swift bindings.

  • Performance focus: Optimized memory, CPU, loading speed
  • Rich types: Text, map, list, movable tree
  • Figma-style canvas: Lists/trees with undo/redo
  • Time travel: Built-in version history

Uses algorithms from Diamond-types (Eg-walker), Automerge (columnar encoding), and Yjs (merge operations).

import { Loro } from "loro-crdt";

const doc = new Loro();
const list = doc.getList("objects");
list.insert(0, { type: "rect", x: 100, y: 100 });

// Sync with another peer
const updates = doc.exportFrom(lastVersion);
otherDoc.import(updates);

Yjs

Mature, widely used. Great for text collaboration.

  • Modular architecture
  • Many editor integrations (Quill, ProseMirror, Monaco)
  • Requires custom UI development

Automerge

JSON document model, multi-language support.

  • Familiar API
  • Historical performance issues (improving)
  • Good for document-centric apps

Canvas Architecture Pattern

┌─────────────────────────────────────┐
│         UI Layer                    │
│   (Fabric.js, Konva, custom)        │
└──────────────┬──────────────────────┘
               │ events
┌──────────────▼──────────────────────┐
│         CRDT Layer (Loro)           │
│   • Object list                     │
│   • Property maps                   │
│   • Z-order tree                    │
└──────────────┬──────────────────────┘
               │ sync
┌──────────────▼──────────────────────┐
│         Transport                   │
│   • WebSocket (server relay)        │
│   • WebRTC (P2P)                    │
│   • IndexedDB (offline)             │
└─────────────────────────────────────┘

Key Patterns

1. Separate Cursors from Document

Cursor positions change constantly. Don’t sync them through the CRDT — use a separate ephemeral channel (WebSocket broadcast).

2. Operation Granularity

Too fine (every keystroke) = network spam. Too coarse (save on blur) = merge conflicts.

Batch operations on idle/debounce.

3. Undo/Redo

CRDTs make this complex — undoing your change shouldn’t undo someone else’s.

Loro has built-in undo manager that handles this correctly.

4. Offline-First

// Save to IndexedDB
const snapshot = doc.exportSnapshot();
await idb.put('doc', snapshot);

// Restore on load
const saved = await idb.get('doc');
if (saved) doc.import(saved);

// Sync when online
socket.on('connect', () => {
  socket.emit('sync', doc.exportFrom(lastSyncVersion));
});

Performance Considerations

  • Document size: CRDTs store history; large docs get heavy
  • Pruning: Periodically compact history on server
  • Lazy loading: Only sync visible portions for large canvases

When to Use What

ScenarioRecommendation
Text editorYjs (mature integrations)
Canvas/whiteboardLoro (performance, tree support)
JSON documentsAutomerge (familiar API)
Need P2PAny CRDT
Simplicity over featuresServer-authoritative (Figma-style)

Sources:

Related: building figma today, rust wasm graphics