Tutorials·Realtime

Build a realtime collaborative doc in an afternoon

From cursor presence to optimistic updates, with copy-paste snippets and the gotchas we learned the hard way.

IH
Iris Halvorsen
DX lead · Apr 28, 2026 · 18 min read

Realtime collaboration used to mean operational transforms, three weeks of debugging, and a paper from 1989 that you read twice and still didn't understand. CRDTs and good libraries have changed that. You can build a usable collaborative editor in an afternoon. Here's how.

What we'll build

  • A document model backed by Yjs.
  • Live cursors for everyone in the doc.
  • Optimistic local edits with automatic conflict resolution.
  • Persistence to a Postgres table via Lovable Cloud.

Step 1 — the data model

Yjs gives you a CRDT for free. Think of it as a JavaScript object that two browsers can edit at the same time and converge on the same final state.

ts
import * as Y from 'yjs'; const doc = new Y.Doc(); const text = doc.getText('content'); text.observe((event) => { console.log('text changed:', text.toString()); });

Step 2 — the transport

Two browsers need to exchange Yjs updates. The simplest transport is a WebSocket relay. We use a small server function that broadcasts updates to everyone subscribed to the same room id.

Step 3 — cursors

Yjs has an Awareness protocol: ephemeral state per user, broadcast alongside doc updates. Use it for cursor position, selection, name, and color.

tsx
awareness.setLocalStateField('cursor', { anchor: editor.selection.anchor, head: editor.selection.head, user: { name: 'Iris', color: '#a78bfa' }, });

Step 4 — persistence

Every minute (or on disconnect) snapshot the Yjs state and write it to a Postgres jsonb column. On load, hydrate the doc from the snapshot before connecting to the relay. That's it. You now have a system where collaboration is live, history is durable, and the merge logic is provably correct.

What we left out

Comments, suggestions, version history, and presence avatars in the toolbar — all are layers on top of the same primitives. Once the awareness channel is open, every additional feature is a shape of data, not a new system.

Build it once and the next collaborative feature in your product takes an afternoon, not a quarter.

fin.
IH
Written by

Iris Halvorsen

DX lead

Works on the parts of Luminax developers touch most: the editor, the previews, the moments between intent and result. Likes long-distance running and short, honest error messages.

Oslo