Collaboration
Connect editors through a shared transport and deploy a collaboration relay
BetterOffice editors synchronize through a Yrs CRDT. Each editor exposes its replica to a collaboration provider, which exchanges updates over a transport you supply. A relay routes those updates between peers in the same room.
How updates flow
- The editor publishes its replica through
collaboration.onReplica. - The format provider converts replica updates to Yjs sync-v1 frames.
- Your transport moves those frames between peers in the same document room.
Use a distinct room ID for every document and format. Updates from different documents or formats are not interoperable.
Deploy the Cloudflare relay
The reference relay
uses one Durable Object per room. It accepts WebSockets at /room/:roomId,
replays retained frames to new peers, and broadcasts new binary frames. Its
wrangler.jsonc
contains the required ROOMS binding and SQLite migration.
From a BetterOffice checkout:
bun install
bun run --filter @betteroffice/collaboration-relay dev
bun run --filter @betteroffice/collaboration-relay deployPoint your browser transport at the deployed Worker origin. The complete demo
adapter is createRoomTransport;
it handles WebSocket reconnects, binary ownership, and backpressure. If you write
your own adapter, implement the CollaborationTransport exported by the format
package. A false return from send means the adapter must emit drain when it
can accept another frame.
Connect an editor
Import CollaborationProvider from the package for your format:
| Format | Provider import |
|---|---|
| DOCX | @betteroffice/docx/collaboration |
| XLSX | @betteroffice/xlsx/collaboration |
| PPTX | @betteroffice/pptx |
| VSDX (source preview) | @betteroffice/vsdx |
VSDX packages are available from source.
Create the provider when the editor publishes its replica:
const onReplica = (replica) => {
provider?.destroy();
transport?.disconnect();
if (!replica) return;
transport = createRoomTransport(relayOrigin, roomId);
provider = new CollaborationProvider(replica, transport);
provider.connect();
};Pass the collaboration options to your editor:
const collaboration = { clientId, initialUpdate, onReplica };
<DocxEditor documentBuffer={docxBytes} collaboration={collaboration} />;
<XlsxEditor file={xlsxBytes} collaboration={collaboration} />;
<PptxEditor file={pptxBytes} fonts={fonts} collaboration={collaboration} />;
<VsdxEditor file={vsdxBytes} fonts={fonts} collaboration={collaboration} />;clientId must be unique per open peer. initialUpdate must represent the
shared starting state for that document. The demo generates these states with
build-collaboration-seeds.ts.
Call provider.destroy() and disconnect the transport when the editor unmounts
or changes rooms.
Production setup
Before deploying, add authentication and room authorization, set connection and storage quotas, and plan how to snapshot or compact long-lived documents. The reference relay retains at most 512 frames or 16 MiB per room.