|
| 1 | +# tldraw sync server |
| 2 | + |
| 3 | +This is a production-ready backend for [tldraw sync](https://tldraw.dev/docs/sync). |
| 4 | + |
| 5 | +- Your client-side tldraw-based app can be served from anywhere you want. |
| 6 | +- This backend uses [Cloudflare Workers](https://developers.cloudflare.com/workers/), and will need |
| 7 | + to be deployed to your own Cloudflare account. |
| 8 | +- Each whiteboard is synced via |
| 9 | + [WebSockets](https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API) to a [Cloudflare |
| 10 | + Durable Object](https://developers.cloudflare.com/durable-objects/). |
| 11 | +- Whiteboards and any uploaded images/videos are stored in a [Cloudflare |
| 12 | + R2](https://developers.cloudflare.com/r2/) bucket. |
| 13 | +- Although unreliated to tldraw sync, this server also includes a component to fetch link previews |
| 14 | + for URLs added to the canvas. |
| 15 | + This is a minimal setup of the same system that powers multiplayer collaboration for hundreds of |
| 16 | + thousands of rooms & users on www.tldraw.com. Because durable objects effectively create a mini |
| 17 | + server instance for every single active room, we've never needed to worry about scale. Cloudflare |
| 18 | + handles the tricky infrastructure work of ensuring there's only ever one instance of each room, and |
| 19 | + making sure that every user gets connected to that instance. We've found that with this approach, |
| 20 | + each room is able to handle about 30 simultaneous collaborators. |
| 21 | + |
| 22 | +## Overview |
| 23 | + |
| 24 | +[](https://www.tldraw.com/ro/Yb_QHJFP9syPZq1YrV3YR?v=-255,-148,2025,1265&p=page) |
| 25 | + |
| 26 | +When a user opens a room, they connect via Workers to a durable object. Each durable object is like |
| 27 | +its own miniature server. There's only ever one for each room, and all the users of that room |
| 28 | +connect to it. When a user makes a change to the drawing, it's sent via a websocket connection to |
| 29 | +the durable object for that room. The durable object applies the change to its in-memory copy of the |
| 30 | +document, and broadcasts the change via websockets to all other connected clients. On a regular |
| 31 | +schedule, the durable object persists its contents to an R2 bucket. When the last client leaves the |
| 32 | +room, the durable object will shut down. |
| 33 | + |
| 34 | +Static assets like images and videos are too big to be synced via websockets and a durable object. |
| 35 | +Instead, they're uploaded to workers which store them in the same R2 bucket as the rooms. When |
| 36 | +they're downloaded, they're cached on cloudflare's edge network to reduce costs and make serving |
| 37 | +them faster. |
| 38 | + |
| 39 | +## Development |
| 40 | + |
| 41 | +To install dependencies, run `yarn`. To start a local development server, run `yarn dev`. This will |
| 42 | +start a [`vite`](https://vitejs.dev/) dev server for the frontend of your application, and a |
| 43 | +[`wrangler`](https://developers.cloudflare.com/workers/wrangler/) dev server for your workers |
| 44 | +backend. The app should now be running at http://localhost:5137 (and the server at |
| 45 | +http://localhost:5172). |
| 46 | + |
| 47 | +The backend worker is under [`worker`](./worker/), and is split across several files: |
| 48 | + |
| 49 | +- **[`worker/worker.ts`](./worker/worker.ts):** the main entrypoint to the worker, defining each |
| 50 | + route available. |
| 51 | +- **[`worker/TldrawDurableObject.ts`](./worker/TldrawDurableObject.ts):** the sync durable object. |
| 52 | + An instance of this is created for every active room. This exposes a |
| 53 | + [`TLSocketRoom`](https://tldraw.dev/reference/sync-core/TLSocketRoom) over websockets, and |
| 54 | + periodically saves room data to R2. |
| 55 | +- **[`worker/assetUploads.ts`](./worker/assetUploads.ts):** uploads, downloads, and caching for |
| 56 | + static assets like images and videos. |
| 57 | +- **[`worker/bookmarkUnfurling.ts`](./worker/bookmarkUnfurling.ts):** extract URL metadata for bookmark shapes. |
| 58 | + |
| 59 | +The frontend client is under [`client`](./client): |
| 60 | + |
| 61 | +- **[`client/App.tsx`](./client/App.tsx):** the main client `<App />` component. This connects our |
| 62 | + sync backend to the `<Tldraw />` component, wiring in assets and bookmark previews. |
| 63 | +- **[`client/multiplayerAssetStore.tsx`](./client/multiplayerAssetStore.tsx):** how does the client |
| 64 | + upload and retrieve assets like images & videos from the worker? |
| 65 | +- **[`client/getBookmarkPreview.tsx`](./client/getBookmarkPreview.tsx):** how does the client fetch |
| 66 | + bookmark previews from the worker? |
| 67 | + |
| 68 | + ## Custom shapes |
| 69 | + |
| 70 | +To add support for custom shapes, see the [tldraw sync custom shapes |
| 71 | +docs](https://tldraw.dev/docs/sync#Custom-shapes--bindings). |
| 72 | + |
| 73 | +## Adding cloudflare to your own repo |
| 74 | + |
| 75 | +If you already have an app using tldraw and want to use the system in this repo, you can copy and |
| 76 | +paste the relevant parts to your own app. |
| 77 | + |
| 78 | +To point your existing client at the server defined in this repo, copy |
| 79 | +[`client/multiplayerAssetStore.tsx`](./client/multiplayerAssetStore.tsx) and |
| 80 | +[`client/getBookmarkPreview.tsx`](./client/getBookmarkPreview.tsx) into your app. Then, adapt the |
| 81 | +code from [`client/App.tsx`](./client/App.tsx) to your own app. When you call `useSync`, you'll need |
| 82 | +to pass it a URL. In development, that's `http://localhost:5172/connect/some-room-id`. We use an |
| 83 | +environment variable set in [`./vite.config.ts`](./vite.config.ts) to set the server URL. |
| 84 | + |
| 85 | +To add the server to your own app, copy the contents of the [`worker`](./worker/) folder and |
| 86 | +[`./wrangler.toml`](./wrangler.toml) into your app. Add the dependencies from |
| 87 | +[`package.json`](./package.json). If you're using TypeScript, you'll also need to adapt |
| 88 | +`tsconfig.worker.json` for your own project. You can run the worker using `wrangler dev` in the same |
| 89 | +folder as `./wrangler.toml`. |
| 90 | + |
| 91 | +## Deployment |
| 92 | + |
| 93 | +To deploy this example, you'll need to create a cloudflare account and create an R2 bucket to store |
| 94 | +your data. Update `bucket_name = 'tldraw-content'` in [`wrangler.toml`](./wrangler.toml) with the |
| 95 | +name of your new bucket. |
| 96 | + |
| 97 | +Run `wrangler deploy` to deploy your backend. This should give you a workers.dev URL, but you can |
| 98 | +also [configure a custom |
| 99 | +domain](https://developers.cloudflare.com/workers/configuration/routing/custom-domains/). |
| 100 | + |
| 101 | +Finally, deploy your client HTML & JavaScript. Create a production build with |
| 102 | +`TLDRAW_WORKER_URL=https://your.workers.domain.com yarn build`. Publish the resulting build (in |
| 103 | +`dist/`) on a host of your choosing - we use [Vercel](https://vercel.com). |
| 104 | + |
| 105 | +When you visit your published client, it should connect to your cloudflare workers domain and sync |
| 106 | +your document across devices. |
| 107 | + |
| 108 | +## License |
| 109 | + |
| 110 | +This project is provided under the MIT license found [here](https://github.com/tldraw/tldraw-sync-cloudflare/blob/main/LICENSE.md). The tldraw SDK is provided under the [tldraw license](https://github.com/tldraw/tldraw/blob/main/LICENSE.md). |
| 111 | + |
| 112 | +## Trademarks |
| 113 | + |
| 114 | +Copyright (c) 2024-present tldraw Inc. The tldraw name and logo are trademarks of tldraw. Please see our [trademark guidelines](https://github.com/tldraw/tldraw/blob/main/TRADEMARKS.md) for info on acceptable usage. |
| 115 | + |
| 116 | +## Distributions |
| 117 | + |
| 118 | +You can find tldraw on npm [here](https://www.npmjs.com/package/@tldraw/tldraw?activeTab=versions). |
| 119 | + |
| 120 | +## Contribution |
| 121 | + |
| 122 | +Please see our [contributing guide](https://github.com/tldraw/tldraw/blob/main/CONTRIBUTING.md). Found a bug? Please [submit an issue](https://github.com/tldraw/tldraw/issues/new). |
| 123 | + |
| 124 | +## Community |
| 125 | + |
| 126 | +Have questions, comments or feedback? [Join our discord](https://discord.gg/rhsyWMUJxd) or [start a discussion](https://github.com/tldraw/tldraw/discussions/new). For the latest news and release notes, visit [tldraw.dev](https://tldraw.dev). |
| 127 | + |
| 128 | +## Contact |
| 129 | + |
| 130 | +Find us on Twitter/X at [@tldraw](https://twitter.com/tldraw). |
0 commit comments