{"id":15311777,"url":"https://github.com/aslemammad/web.tldraw.com","last_synced_at":"2026-04-23T05:30:22.253Z","repository":{"id":256644499,"uuid":"855403803","full_name":"Aslemammad/web.tldraw.com","owner":"Aslemammad","description":null,"archived":false,"fork":false,"pushed_at":"2024-09-12T07:38:44.000Z","size":484,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-02-16T04:04:53.955Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Aslemammad.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2024-09-10T20:10:22.000Z","updated_at":"2025-01-29T15:16:02.000Z","dependencies_parsed_at":"2024-09-12T07:09:33.424Z","dependency_job_id":"d2d05261-db73-4eb5-b00d-a8fd5a4ccf32","html_url":"https://github.com/Aslemammad/web.tldraw.com","commit_stats":{"total_commits":5,"total_committers":1,"mean_commits":5.0,"dds":0.0,"last_synced_commit":"43638100a042a05893c4cf74beaf54b5a5d65d5d"},"previous_names":["aslemammad/web.tldraw.com"],"tags_count":0,"template":false,"template_full_name":"tldraw/tldraw-sync-cloudflare","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Aslemammad%2Fweb.tldraw.com","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Aslemammad%2Fweb.tldraw.com/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Aslemammad%2Fweb.tldraw.com/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Aslemammad%2Fweb.tldraw.com/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Aslemammad","download_url":"https://codeload.github.com/Aslemammad/web.tldraw.com/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":240035644,"owners_count":19737602,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-10-01T08:34:30.463Z","updated_at":"2026-04-23T05:30:22.199Z","avatar_url":"https://github.com/Aslemammad.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"[Stackblitz link](https://stackblitz.com/github.com/Aslemammad/web.tldraw.com/)\n\n# tldraw sync server\n\nThis is a production-ready backend for [tldraw sync](https://tldraw.dev/docs/sync).\n\n- Your client-side tldraw-based app can be served from anywhere you want.\n- This backend uses [Cloudflare Workers](https://developers.cloudflare.com/workers/), and will need\n  to be deployed to your own Cloudflare account.\n- Each whiteboard is synced via\n  [WebSockets](https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API) to a [Cloudflare\n  Durable Object](https://developers.cloudflare.com/durable-objects/).\n- Whiteboards and any uploaded images/videos are stored in a [Cloudflare\n  R2](https://developers.cloudflare.com/r2/) bucket.\n- Although unreliated to tldraw sync, this server also includes a component to fetch link previews\n  for URLs added to the canvas.\n\nThis is a minimal setup of the same system that powers multiplayer collaboration for hundreds of\nthousands of rooms \u0026 users on www.tldraw.com. Because durable objects effectively create a mini\nserver instance for every single active room, we've never needed to worry about scale. Cloudflare\nhandles the tricky infrastructure work of ensuring there's only ever one instance of each room, and\nmaking sure that every user gets connected to that instance. We've found that with this approach,\neach room is able to handle about 30 simultaneous collaborators.\n\n## Overview\n\n[![architecture](./arch.png)](https://www.tldraw.com/ro/Yb_QHJFP9syPZq1YrV3YR?v=-255,-148,2025,1265\u0026p=page)\n\nWhen a user opens a room, they connect via Workers to a durable object. Each durable object is like\nits own miniature server. There's only ever one for each room, and all the users of that room\nconnect to it. When a user makes a change to the drawing, it's sent via a websocket connection to\nthe durable object for that room. The durable object applies the change to its in-memory copy of the\ndocument, and broadcasts the change via websockets to all other connected clients. On a regular\nschedule, the durable object persists its contents to an R2 bucket. When the last client leaves the\nroom, the durable object will shut down.\n\nStatic assets like images and videos are too big to be synced via websockets and a durable object.\nInstead, they're uploaded to workers which store them in the same R2 bucket as the rooms. When\nthey're downloaded, they're cached on cloudflare's edge network to reduce costs and make serving\nthem faster.\n\n## Development\n\nTo install dependencies, run `yarn`. To start a local development server, run `yarn dev`. This will\nstart a [`vite`](https://vitejs.dev/) dev server for the frontend of your application, and a\n[`wrangler`](https://developers.cloudflare.com/workers/wrangler/) dev server for your workers\nbackend. The app should now be running at http://localhost:5137 (and the server at\nhttp://localhost:5172).\n\nThe backend worker is under [`worker`](./worker/), and is split across several files:\n\n- **[`worker/worker.ts`](./worker/worker.ts):** the main entrypoint to the worker, defining each\n  route available.\n- **[`worker/TldrawDurableObject.ts`](./worker/TldrawDurableObject.ts):** the sync durable object.\n  An instance of this is created for every active room. This exposes a\n  [`TLSocketRoom`](https://tldraw.dev/reference/sync-core/TLSocketRoom) over websockets, and\n  periodically saves room data to R2.\n- **[`worker/assetUploads.ts`](./worker/assetUploads.ts):** uploads, downloads, and caching for\n  static assets like images and videos.\n- **[`worker/bookmarkUnfurling.ts`](./worker/bookmarkUnfurling.ts):** extract URL metadata for bookmark shapes.\n\nThe frontend client is under [`client`](./client):\n\n- **[`client/App.tsx`](./client/App.tsx):** the main client `\u003cApp /\u003e` component. This connects our\n  sync backend to the `\u003cTldraw /\u003e` component, wiring in assets and bookmark previews.\n- **[`client/multiplayerAssetStore.tsx`](./client/multiplayerAssetStore.tsx):** how does the client\n  upload and retrieve assets like images \u0026 videos from the worker?\n- **[`client/getBookmarkPreview.tsx`](./client/getBookmarkPreview.tsx):** how does the client fetch\n  bookmark previews from the worker?\n\n## Custom shapes\n\nTo add support for custom shapes, see the [tldraw sync custom shapes\ndocs](https://tldraw.dev/docs/sync#Custom-shapes--bindings).\n\n## Adding cloudflare to your own repo\n\nIf you already have an app using tldraw and want to use the system in this repo, you can copy and\npaste the relevant parts to your own app.\n\nTo point your existing client at the server defined in this repo, copy\n[`client/multiplayerAssetStore.tsx`](./client/multiplayerAssetStore.tsx) and\n[`client/getBookmarkPreview.tsx`](./client/getBookmarkPreview.tsx) into your app. Then, adapt the\ncode from [`client/App.tsx`](./client/App.tsx) to your own app. When you call `useSync`, you'll need\nto pass it a URL. In development, that's `http://localhost:5172/connect/some-room-id`. We use an\nenvironment variable set in [`./vite.config.ts`](./vite.config.ts) to set the server URL.\n\nTo add the server to your own app, copy the contents of the [`worker`](./worker/) folder and\n[`./wrangler.toml`](./wrangler.toml) into your app. Add the dependencies from\n[`package.json`](./package.json). If you're using TypeScript, you'll also need to adapt\n`tsconfig.worker.json` for your own project. You can run the worker using `wrangler dev` in the same\nfolder as `./wrangler.toml`.\n\n## Deployment\n\nTo deploy this example, you'll need to create a cloudflare account and create an R2 bucket to store\nyour data. Update `bucket_name = 'tldraw-content'` in [`wrangler.toml`](./wrangler.toml) with the\nname of your new bucket.\n\nRun `wrangler deploy` to deploy your backend. This should give you a workers.dev URL, but you can\nalso [configure a custom\ndomain](https://developers.cloudflare.com/workers/configuration/routing/custom-domains/).\n\nFinally, deploy your client HTML \u0026 JavaScript. Create a production build with\n`TLDRAW_WORKER_URL=https://your.workers.domain.com yarn build`. Publish the resulting build (in\n`dist/`) on a host of your choosing - we use [Vercel](https://vercel.com).\n\nWhen you visit your published client, it should connect to your cloudflare workers domain and sync\nyour document across devices.\n\n## License\n\nWhilst the code in this template is available under the MIT license, `tldraw` and `@tldraw/sync` are\nunder the [tldraw license](https://github.com/tldraw/tldraw/blob/main/LICENSE.md) which does not\nallow their use for commercial purposes. To purchase an alternative license for commercial use,\ncontact [sales@tldraw.com](mailto:sales@tldraw.com).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Faslemammad%2Fweb.tldraw.com","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Faslemammad%2Fweb.tldraw.com","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Faslemammad%2Fweb.tldraw.com/lists"}