如何用 Cloudflare Durable Objects 模板部署 tldraw 多人协作白板后端
如果你的 tldraw 应用需要多人实时协作(共享画布、光标、在线状态),就必须自托管一个 tldraw sync 后端。tldraw 文档给出的两条自托管路径中,官方推荐的是直接部署 tldraw 仓库里的 Cloudflare 模板:它使用 Cloudflare Workers + Durable Objects 为每个房间提供独立的 WebSocket 服务,房间状态自动持久化到 Durable Object 内置的 SQLite 存储,图片和视频等大资源存到 Cloudflare R2 桶。部署完成后你会得到一个可用的多人协作白板后端,前端客户端通过 useSync 连接到它即可。
tldraw sync Cloudflare 模板架构:用户经 Workers 路由连接到各房间独占的 Durable Object,房间状态持久化在 SQLite,静态资源存放在 R2
后端由哪些部分组成
理解部署前的组件划分,方便验证时逐一对上:
/api/connect/:roomId:WebSocket 同步入口。请求被路由到该房间的 Durable Object,每个房间有且只有一个实例,所有连接该房间的用户都接到同一实例上(见 worker/worker.ts)。TldrawDurableObject:同步 Durable Object,暴露TLSocketRoom,负责在 WebSocket 客户端之间同步变更,并把房间状态写入内置 SQLite 存储,重启和休眠后数据仍在(见 worker/TldrawDurableObject.ts)。/api/uploads/:uploadId:图片、视频的上传与下载,文件存入 R2 桶并走 Cloudflare 边缘缓存(见 worker/assetUploads.ts)。/api/unfurl:书签形状的链接预览服务,提取 URL 元数据。- 客户端:通过
useSynchook 建连,模板中连接地址是${window.location.origin}/api/connect/${roomId}(见 client/pages/Room.tsx)。
准备工作
-
需要一个 Cloudflare 账号,并在其中创建一个 R2 桶,用于存放上传的图片、视频(模板 README 明确要求先建账号、再建 R2 桶)。
-
获取模板代码。两种方式任选其一:
-
使用脚手架命令(Multiplayer starter kit 文档给出的方式,它拉取的就是本模板):
npm create tldraw@latest -- --template multiplayer -
直接复制当前仓库中的 templates/sync-cloudflare 目录作为起点。
-
-
进入模板目录安装依赖:
yarn
可选:先在本地跑通
本地开发用 yarn dev,它会启动一个 vite 开发服务器,通过 Cloudflare vite 插件同时运行前端和 Workers 后端,应用与服务器都应运行在 http://localhost:5137。先本地确认协作行为正常,再走部署流程,可以排除应用自身的问题。
修改 wrangler.toml 中的部署配置
wrangler.toml 是本次部署唯一必须修改的配置文件。模板当前内容与需要替换的部分:
name = "multiplayer-template"
main = "worker/worker.ts"
compatibility_date = "2025-05-08"
compatibility_flags = ["nodejs_compat"]
[[routes]]
pattern = "multiplayer.templates.tldraw.dev"
custom_domain = true
[assets]
not_found_handling = "single-page-application"
directory = "./dist/client"
[durable_objects]
bindings = [{ name = "TLDRAW_DURABLE_OBJECT", class_name = "TldrawDurableObject" }]
[[migrations]]
tag = "v1"
new_sqlite_classes = ["TldrawDurableObject"]
[[r2_buckets]]
binding = 'TLDRAW_BUCKET'
bucket_name = 'multiplayer-template'
preview_bucket_name = 'multiplayer-template-preview'
需要处理的三处:
bucket_name(必改):按模板 README 的说明,将其替换为你新建的 R2 桶名。注意preview_bucket_name是本地预览用的桶,模板当前指向multiplayer-template-preview,本地跑wrangler dev预览时同样需要对应可用的桶。[[routes]]自定义域名:模板里绑定的multiplayer.templates.tldraw.dev是官方模板自己的域名。部署到你自己的 Cloudflare 账号后,默认会得到一个 workers.dev 地址;如果你想用自己的域名,把该节改成你的域名(README 指出也可以之后单独配置自定义域名)。name:模板名为multiplayer-template,建议改成你自己的应用名,避免与已有 Worker 冲突。
其余部分——Durable Object 绑定、new_sqlite_classes 迁移声明、[assets] 单页应用路由——保持模板原样即可。[assets] 一节说明 yarn build 产出的前端会一并作为静态资源部署,所以一次部署同时包含后端 Worker 和前端应用。
构建并部署到 Cloudflare
在模板目录执行:
yarn build
yarn wrangler deploy
yarn build 产出生产构建,yarn wrangler deploy 把后端 Worker 与前端应用一起部署到 Cloudflare,成功后你会得到一个 workers.dev 地址。部署目标地址就是你的多人协作后端入口。
验证部署结果
文档没有提供独立的健康检查命令,可用以下行为逐项确认后端工作正常:
- 打开入口地址:访问 workers.dev 地址应能看到白板前端页面,进入某个房间后 URL 带上房间 ID。
- 多人同步:在两个浏览器窗口(或无痕窗口)打开同一个房间链接。模板的协作表现是:房间内出现其他用户的在线状态(头像、姓名)和实时光标;一侧的修改会经 Durable Object 即时广播给另一侧。如果两人互不可见,检查是否同一房间 ID、两个客户端连的是不是同一后端。
- 持久化:在房间中画入内容,刷新或重新打开同一房间链接,内容仍在。这是 Durable Object SQLite 存储在工作(房间状态自动持久化,无需手动保存逻辑)。
- 资源上传:往画布里放入图片,确认其可正常回显,对应请求走
/api/uploads/并落到你的 R2 桶。 - 书签预览:粘贴一个 URL 生成书签形状,应能拉取到链接预览元数据(
/api/unfurl)。
一个文档明确给出的失败现象值得记住:若客户端与服务端的 tldraw 版本不匹配,tldraw 会显示 "please refresh the page" 提示。因此部署新客户端版本前,先保证新后端已上线。
已知限制
- 版本一致性是硬要求:客户端与后端的 tldraw 版本必须一致。官方不保证后端永久向后兼容,偶尔会发布后端无法支持旧客户端的版本。文档建议后端与新客户端同步更新,并且新后端要先于新客户端上线。
- 模板不含生产能力:同步文档明确说明,以下能力模板未提供,需要自行添加:身份认证与授权、上传的限流与大小限制、按时间留存文档快照、房间列表与搜索。
- 单房间规模:模板 README 提到 tldraw.com 使用同一套系统,每个房间大约能支撑 50 人同时协作。这个数字来自 README 的表述,作为规模参考而非承诺值。
- 模板 Worker 开启了 CORS(因为示例中 Worker 与客户端可能分开部署),源码注释提醒:接入自己的应用时应把 CORS 限制到自己的域名。
替代路径与延伸阅读
- 如果不想用 Durable Objects,
@tldraw/sync-core也支持接入任意支持 WebSocket 的 JavaScript 后端,仓库中有 simple-server-example 作为参考实现(配合InMemorySyncStorage或SQLiteSyncStorage)。 - 已有自研应用想复用这套后端时,模板 README 的 "Adding cloudflare to your own repo" 一节给出做法:把
worker/目录和wrangler.toml拷入你的应用,加上 package.json 中的相关依赖,用wrangler dev本地运行;客户端侧拷贝 client/multiplayerAssetStore.tsx 和 client/getBookmarkPreview.tsx,并把其中/api/地址改指向你的新服务器。 - 自定义形状需要客户端与服务端两侧同时注册 schema,完整说明见 tldraw sync 文档和 Multiplayer starter kit 文档。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0629
MiniCPM5-2BMiniCPM5-2B 是一款面向端侧、本地部署和资源受限场景的 2B 稠密 Transformer,能够达到同尺寸开源模型 SOTA 水平。Markdown00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
HivisionIDPhotos⚡️HivisionIDPhotos: a lightweight and efficient AI ID photos tools. 一个轻量级的AI证件照制作算法。Python09
DragonOSDragonOS is an operating system developed from scratch using Rust, with Linux compatibility. It is designed for **Serverless** scenarios. 使用Rust从0自研内核,具有Linux兼容性的操作系统,面向云计算Serverless场景而设计。Rust00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00