CanVas
Realtime collaborative rooms pairing a shared block-editor document with a shared Excalidraw canvas — join by room name, no accounts, synced live via Yjs over a Cloudflare Worker.
- Services
- Product DesignEngineering
- Industries
- RealtimeDev Tools
- Tools
- TypeScriptYjsCloudflare WorkersWebSockets
- Highlight
- A URL is the whole account system.
- Live
- co-canvas-web.vercel.app
Accounts are the tax you pay before the useful part
Two people want to think about the same thing at the same time. Between them and that stands a sign-up form, an email confirmation, a workspace, an invite, a permission model and a seat licence. Every one of those exists for a real reason and none of them is the thing anyone came to do.
Co-Canvas removes all of it. You type a room name to join it; if it does not exist yet, typing it creates it. The URL is the whole account system — sharing it grants access, presence cursors and all.
Two surfaces, because one would be a bad version of both
The obvious build is a single canvas that also accepts text. It is obvious and it is wrong, and the README says why in one line: "Text and flows, canvas is spatial — so cramming both into one editor makes a bad version of each."
So a room is a BlockNote document and an Excalidraw canvas, bound together and synchronised as one. Different people can be on different surfaces at the same time, which turns out to be how pairs actually work — one writing while the other draws, both watching the same room.
Each room slug maps to a Cloudflare Durable Object holding the Yjs document, so a room survives with zero connected clients.
- Sync
- Yjs CRDTsy-partyserverCloudflare Durable Objects
- Surfaces
- BlockNote documentExcalidraw canvasPresence cursors
- Access
- No accountsNo passwordsThe URL is the grant
- Stack
- Next.js on VercelCloudflare Workerpnpm workspaces
What it cost
Durable Objects are the design, not the deployment. A room that has to survive with nobody in it needs somewhere to live, and the choice between "a database row you load and save" and "an object that simply stays" decides the shape of everything above it. Picking the second made presence, persistence and the room lifecycle one problem rather than three.
The two-surface binding was the fiddly part. Yjs handles the merge; what it does not handle is two editors with different ideas about what a document is, sharing one awareness channel so that a cursor on the canvas and a cursor in the text read as the same person.
Where it does not work yet
The trade is stated plainly rather than hidden: no accounts means no recovery. Lose the room name and the room is gone, because there is no list of your rooms — there is no you. That is the correct trade for a scratchpad and the wrong one for anything you would be upset to lose, and the product should probably say so louder than it does.
The roadmap under docs/ is honest about what is next. This is a working thing rather than a finished one.