This is a production-ready backend for tldraw sync.
- Your client-side tldraw-based app can be served from anywhere you want.
- This backend uses Cloudflare Workers, and will need to be deployed to your own Cloudflare account.
- Each whiteboard is synced via WebSockets to a Cloudflare Durable Object.
- Whiteboards and any uploaded images/videos are stored in a Cloudflare R2 bucket.
- Although unreliated to tldraw sync, this server also includes a component to fetch link previews for URLs added to the canvas. This is a minimal setup of the same system that powers multiplayer collaboration for hundreds of thousands of rooms & users on www.tldraw.com. Because durable objects effectively create a mini server instance for every single active room, we've never needed to worry about scale. Cloudflare handles the tricky infrastructure work of ensuring there's only ever one instance of each room, and making sure that every user gets connected to that instance. We've found that with this approach, each room is able to handle about 30 simultaneous collaborators.
When a user opens a room, they connect via Workers to a durable object. Each durable object is like its own miniature server. There's only ever one for each room, and all the users of that room connect to it. When a user makes a change to the drawing, it's sent via a websocket connection to the durable object for that room. The durable object applies the change to its in-memory copy of the document, and broadcasts the change via websockets to all other connected clients. On a regular schedule, the durable object persists its contents to an R2 bucket. When the last client leaves the room, the durable object will shut down.
Static assets like images and videos are too big to be synced via websockets and a durable object. Instead, they're uploaded to workers which store them in the same R2 bucket as the rooms. When they're downloaded, they're cached on cloudflare's edge network to reduce costs and make serving them faster.
To install dependencies, run yarn
. To start a local development server, run yarn dev
. This will
start a vite
dev server for the frontend of your application, and a
wrangler
dev server for your workers
backend. The app should now be running at http://localhost:5137 (and the server at
http://localhost:5172).
The backend worker is under worker
, and is split across several files:
worker/worker.ts
: the main entrypoint to the worker, defining each route available.worker/TldrawDurableObject.ts
: the sync durable object. An instance of this is created for every active room. This exposes aTLSocketRoom
over websockets, and periodically saves room data to R2.worker/assetUploads.ts
: uploads, downloads, and caching for static assets like images and videos.worker/bookmarkUnfurling.ts
: extract URL metadata for bookmark shapes.
The frontend client is under client
:
-
client/App.tsx
: the main client<App />
component. This connects our sync backend to the<Tldraw />
component, wiring in assets and bookmark previews. -
client/multiplayerAssetStore.tsx
: how does the client upload and retrieve assets like images & videos from the worker? -
client/getBookmarkPreview.tsx
: how does the client fetch bookmark previews from the worker?
To add support for custom shapes, see the tldraw sync custom shapes docs.
If you already have an app using tldraw and want to use the system in this repo, you can copy and paste the relevant parts to your own app.
To point your existing client at the server defined in this repo, copy
client/multiplayerAssetStore.tsx
and
client/getBookmarkPreview.tsx
into your app. Then, adapt the
code from client/App.tsx
to your own app. When you call useSync
, you'll need
to pass it a URL. In development, that's http://localhost:5172/connect/some-room-id
. We use an
environment variable set in ./vite.config.ts
to set the server URL.
To add the server to your own app, copy the contents of the worker
folder and
./wrangler.toml
into your app. Add the dependencies from
package.json
. If you're using TypeScript, you'll also need to adapt
tsconfig.worker.json
for your own project. You can run the worker using wrangler dev
in the same
folder as ./wrangler.toml
.
To deploy this example, you'll need to create a cloudflare account and create an R2 bucket to store
your data. Update bucket_name = 'tldraw-content'
in wrangler.toml
with the
name of your new bucket.
Run wrangler deploy
to deploy your backend. This should give you a workers.dev URL, but you can
also configure a custom
domain.
Finally, deploy your client HTML & JavaScript. Create a production build with
TLDRAW_WORKER_URL=https://your.workers.domain.com yarn build
. Publish the resulting build (in
dist/
) on a host of your choosing - we use Vercel.
When you visit your published client, it should connect to your cloudflare workers domain and sync your document across devices.
This project is provided under the MIT license found here. The tldraw SDK is provided under the tldraw license.
Copyright (c) 2024-present tldraw Inc. The tldraw name and logo are trademarks of tldraw. Please see our trademark guidelines for info on acceptable usage.
You can find tldraw on npm here.
Please see our contributing guide. Found a bug? Please submit an issue.
Have questions, comments or feedback? Join our discord or start a discussion. For the latest news and release notes, visit tldraw.dev.
Find us on Twitter/X at @tldraw.