Skip to Content
Real-time sync

Real-time sync

Sync is built into the data model rather than sold as a service. Every change emits a JSON-safe diff; ship it to peers over any transport and apply theirs as they arrive.

The whole client

const { store } = board.editor store.listen((diff) => socket.send(JSON.stringify(diff)), { source: 'user' }) socket.onmessage = (e) => store.applyDiff(JSON.parse(e.data), 'remote')

The source tag is the load-bearing detail:

  • The listener filter { source: 'user' } means you only broadcast what this client did — no echo loops.
  • Applying with 'remote' keeps peer changes out of the local undo stack. When a user hits ⌘Z, their own last stroke vanishes — not a collaborator’s.

Any transport works: WebSocket, WebRTC data channel, BroadcastChannel (try it across two tabs), or your own message bus.

The relay

The server never interprets documents — it forwards bytes:

import { WebSocketServer } from 'ws' const wss = new WebSocketServer({ port: 8080 }) wss.on('connection', (ws) => { ws.on('message', (data) => { for (const peer of wss.clients) { if (peer !== ws && peer.readyState === 1) peer.send(data.toString()) } }) })

New peers catch up with a snapshot: send them store.getSnapshot(), they call store.loadSnapshot(snap, 'remote'), then live diffs flow.

Conflicts

Records update whole, so concurrency resolves at record granularity — last writer wins per shape. For a whiteboard this matches intuition: two people rarely edit the same shape at once, and when they do, one winning cleanly beats a merge artifact. Freehand strokes never conflict at all — each stroke is a new record.

If you outgrow that (offline-first editing, heavy contention), you can layer a CRDT or server reconciliation on top of the same diff stream — the diff already captures intent, and nothing in the engine assumes an authority model.

React Native peers

@quickdrawjs/react-native emits the same diffs, so a phone and a browser can share one relay:

<Quickdraw onChange={(diff, source) => source === 'user' && socket.send(JSON.stringify(diff))} /> // board.current.applyDiff(remoteDiff) on message
Last updated on