Real-time collaboration
Edit a page together, live — presence, shared cursors of change, and a lightweight collaboration service that keeps Git byte-exact.
Edit a page together, live — presence, shared cursors of change, and a lightweight collaboration service that keeps Git byte-exact.
The editor is collaborative: two people (or two of your own tabs) can open the same page and edit it at once, in either Visual or Source mode, and see each other’s changes as they happen. A small presence indicator in the editor toolbar shows who else is on the page, and in both Visual and Source mode you see each collaborator’s live cursor — coloured and labelled with their name — moving as they type.
Collaboration is a strict enhancement. With no collaboration service configured, same-browser tabs still stay in sync, and everything else works exactly as before — it can never break editing.
The shared value for a page is its raw MDX text — the whole file, character for character. Both editor modes are views onto that one text:
{expressions}, imports — is carried verbatim, so a collaborator typing next to it can’t corrupt
it.Two tabs or windows in the same browser sync instantly with zero setup, over a built-in channel. Useful for editing a page while previewing it beside itself.
Real multiplayer between different people/devices runs over a small always-on service (below). When it’s configured, the editor connects to it automatically; when it isn’t, the editor falls back to same-browser sync.
Either way, your edits still autosave to the page’s draft on your branch, and nothing is live until you publish — the same draft-and-publish flow as solo editing. The shared document is just live coordination; the draft (and Git) remains the record.
Cross-machine editing uses a standalone Hocuspocus (Yjs) WebSocket service — apps/collab in
the repo. It’s the one always-on piece a serverless host can’t run, so it lives on its own. It is
deliberately minimal: it relays the shared document and tracks presence, and holds no
accounts and no content — the app authorizes editing and persists drafts.
Locally it’s the collab service in docker-compose (docker compose up). In production run
the same container on any host that can hold a WebSocket open — a small always-on machine on
Fly, Railway, Render, or your own box. It has no database of its own.
Set two environment variables (see .env.example):
COLLAB_JWT_SECRET — a shared secret. The app signs a short-lived, single-page token with
it; the service verifies it. Use the same value in both places.NEXT_PUBLIC_COLLAB_URL — the WebSocket URL the browser connects to (e.g.
ws://127.0.0.1:1234 locally, wss://collab.yourdomain in production).With both set, the editor connects automatically. Leave them unset and collaboration simply falls back to same-browser sync — no errors, no configuration required.
The service can’t see your login, so the app does the check. When you open a page, the app confirms
you’re allowed to edit that site (the same gate as every editor action), then mints a token scoped
to exactly that page (site · branch · path) that expires in a few minutes. The service accepts
a connection only if the token is valid and matches the page being opened — so one editor can never
join another site’s document.
Because the service runs as a single container you deploy yourself, multiplayer works the same everywhere Papervine runs — no third-party collaboration SaaS required.