PartyKit 与 PartyServer:能力、场景及 Cloudflare / TanStack 集成
原始资料:PartyKit 官方文档 · PartyKit 官网
AI 参与说明(Agent:Codex):本文由 Codex 根据上述资料及 Cloudflare、PartyServer、TanStack 的一手文档调研、撰写并校验。资料核对日期为 2026-09-28;PartyKit 文档部分页面标注的最近更新日期为 2025-03-26,涉及平台能力时请继续核对其最新说明。本次运行的完整模型标识与 reasoning effort 未取得运行记录。
标签整理(2026-10-16,Agent:Codex):本次仅补齐分类标签并核对元数据,未改写或重新校验原文技术结论。运行记录:模型
gpt-6.1-sol,reasoning effortultra,执行入口 Codex Desktop,提供方openai;CLI 版本0.162.0-alpha.17.2(不代表桌面 App 版本)。
修订说明(2026-09-28,Agent:Codex):按读者反馈补足 PartyKit 与 PartyServer 的定义、能力及场景,再讨论 Cloudflare 和 TanStack 集成;完整模型标识与 reasoning effort 未取得运行记录。
先建立概念: PartyKit 是开发和部署实时房间服务的平台;PartyServer 是在自己的 Cloudflare Worker 中编写这类房间服务的 Durable Objects 库。二者都适合按文档、会话或活动 ID 组织实时连接,但部署方式、API 和资源所有权不同。理解它们各自能解决什么问题后,再考虑现有 Workers / Durable Objects / TanStack 工程如何接入。
| 英文术语 | 中文名称 | 简要解释 |
|---|---|---|
| PartyKit | PartyKit | 提供房间服务 API、CLI 和部署平台的项目;其 room 由 Durable Object 承载。 |
| Party | PartyKit 服务类型 | URL /parties/main/document-42 中的 main,同一项目可以定义多个。 |
| room | 实时房间 | 由 document-42 一类业务 ID 寻址的隔离实例。 |
| Durable Object | 持久化对象 | Cloudflare 中按对象 ID 定位、集中协调请求并持有私有存储的运行实例。 |
| PartyServer | PartyServer | Cloudflare 维护的 Durable Objects 库,为房间路由、WebSocket 生命周期和广播提供封装。 |
| PartySocket | PartySocket | 可选的 WebSocket 客户端库,提供重连等能力;也能连接其他 WebSocket 服务。 |
| cloud-prem | 自有账户部署 | 使用 PartyKit CLI,把 PartyKit 项目部署到自己的 Cloudflare 账户。 |
PartyKit 是什么,能做什么
PartyKit 是面向实时、多人应用的开发与托管平台。开发者用 JavaScript / TypeScript 实现 Party.Server,用 partykit CLI 本地开发和部署;平台负责按 ID 创建、寻址和运行 room。底层运行环境建立在 Cloudflare workerd 与 Durable Objects 之上,但使用 PartyKit 托管服务不等于自己维护一个 Wrangler 项目。What is PartyKit · How PartyKit works
一个 room 是可编程的小型服务器,不只是转发 WebSocket 消息的中继。它既能处理连接与消息,也能响应 HTTP 请求;room.broadcast() 把事件送给房间中的连接,room.storage 为该房间保存数据。onStart、onConnect、onMessage、onRequest 和 onAlarm 等 hooks 分别覆盖初始化、实时连接、普通请求和定时任务。PartyKit 还提供可选的 PartySocket 客户端、用于 Yjs 协同编辑的独立 Y-PartyKit 包,以及本地开发、部署预览与环境变量工作流。Party.Server API · PartySocket API · Y-PartyKit API
以共同编辑 document-42 为例,运行过程可以拆成四步:
- 两个浏览器连接
/parties/main/document-42。main选择服务类型,document-42选择 room;同一 ID 的连接进入同一个逻辑房间,另一个文档 ID 则进入另一房间。多 Party 路由 - room 在 WebSocket 握手和收到消息时检查身份、文档权限与消息内容;通过检查的修改被广播给当前房间的其他连接。
- 如果修改必须在断线后仍然存在,room 把权威状态或快照写进持久存储;新连接可通过 HTTP 或连接后的消息取得快照,再继续接收增量事件。
- 空闲休眠、实例重建或重新部署后,从持久存储恢复。不能把 class 字段里的内存数据当作永久记录,也不能把“同一逻辑 room”误读成“永远是同一个进程”。房间寻址 · Storage 指南 · Hibernation 指南
这四步是一个应用设计示例,不是 PartyKit 自动完成的文档编辑产品。连接、广播和 room 存储由平台提供;身份与权限、业务消息格式、冲突解决、历史版本和跨 room 检索仍由应用设计。多人同时编辑同一份文本或白板时,可评估 Y-PartyKit 与 Yjs,而不应只把原始字符修改广播给其他客户端。Authentication 指南 · Y-PartyKit API
PartyServer 是什么,能做什么
PartyServer 是 Cloudflare 维护的 npm 包 partyserver,用于在自己的 Worker 项目中编写房间式 Durable Object。类从 Server 继承,仍是 Durable Object;Wrangler 配置负责声明 class、binding 与存储。它不是 PartyKit 的托管服务,也不需要通过 PartyKit CLI 部署。PartyServer README
相比直接编写原生 Durable Object,PartyServer 提供按 room 名称定位的路由、连接和请求生命周期 hooks、休眠与非休眠连接的统一处理,以及向全部或部分连接广播的 API。开发者仍掌控 Worker 入口、env bindings、this.ctx.storage、身份校验和业务数据模型。routePartykitRequest() 只是可选的 PartyKit 风格 URL 路由;PartyServer 也允许将服务器标识与 URL 解耦。浏览器可使用原生 WebSocket,也可使用能连接其他 WebSocket 服务的 PartySocket。PartyServer README · PartySocket API
两套服务端 API 不能混用:PartyKit 实现 Party.Server 接口,onMessage(message, sender) 从 this.room.storage 读写;PartyServer 的 Server 继承 Durable Object,onMessage(connection, message) 从 this.ctx.storage 读写。后文的可运行示例使用的是 PartyServer。如果只需要一个计数器或已有 DO RPC,直接使用原生 Durable Objects 可能更简单;需要大量房间、WebSocket 生命周期和广播时,PartyServer 的封装才更有价值。PartyKit Server API · PartyServer README
哪些场景适合房间模型
关键判断是:多方是否需要围绕同一个业务 ID 交换低延迟消息,并由一个房间协调状态? 下表中的“房间负责”是设计建议,持久化和权限仍需按实际业务实现。
| 场景 | room 如何划分及负责什么 | 额外工作与边界 |
|---|---|---|
| 在线成员、协作光标、观看进度 | 每份文档或每个活动一个 room,跟踪连接并广播短暂状态 | 重连时清理过期状态;不必把每次光标移动写进持久数据库 |
| 实时投票、聊天、直播互动 | 每场活动或会话一个 room,校验输入、协调顺序、广播新结果 | 需定义重复投票、消息历史、限流和掉线恢复;持久记录不能只放内存 |
| 文档、画布协同编辑 | 每份文档一个 room,把并发编辑事件送给参与者 | 用 Y-PartyKit 或 Y-PartyServer 等方案处理 CRDT,并设计快照与恢复 |
| 多人游戏或任务进度 | 每局游戏或每个任务一个 room,协调参与者与事件 | 长时任务本身的可靠执行、跨 room 排名或全站报表需另设机制 |
这些场景与 PartyKit 官方示例 中的在线头像、投票、多人编辑和游戏相符。反过来,纯 CRUD、可由 HTTP 周期刷新满足的页面、全站查询或文件存储,本身不需要实时 room。房间内的协调能力也不能替代认证、可查询数据库或可靠异步任务。
再选择实现与部署路径
一个 PartyKit room 由一个 Durable Object 承载;PartyServer 则让你直接在自有 Worker 中管理这类 Durable Object。 选择前先确认实时服务与现有 Worker 要不要共享部署、bindings 和权限边界。PartyKit 运行模型 · PartyServer README
| 路径 | 运行与部署 | 怎样接入现有 Worker | 适合的起点 |
|---|---|---|---|
| PartyKit 托管平台 | partykit CLI 部署;PartyKit 管理资源和路由 | 通过公开的 HTTP / WebSocket 地址通信 | 应用不依赖现有 Cloudflare bindings,想快速增加实时能力 |
| PartyKit cloud-prem | 同一套 PartyKit 项目通过 CLI 部署到自有 Cloudflare 账户 | 仍按独立服务考虑,通过 HTTP / WebSocket 地址通信 | 需要自有账户或域名,同时保留 PartyKit 开发模式 |
| PartyServer | partyserver 作为依赖放进自己的 Wrangler 项目;DO class、binding 和存储声明由自己管理 | Worker 直接路由到本项目的 Durable Object,使用 env 中的其他 bindings | 已有 Workers、D1、R2、Queues 等资源,想把实时房间纳入同一工程 |
| 原生 Durable Objects | 自己实现 DO class、WebSocket、存储与路由 | Worker 直接通过 DO binding 调用 | 只需少量对象或自定义协议,不需要房间式辅助 API |
这里有一个容易误解的地方:部署到同一个 Cloudflare 账户,不等于自动获得现有 Worker 的 bindings。 2026-09-28 可访问的 PartyKit cloud-prem 指南仍把 KV、R2、D1 等更多 bindings,以及“在 Wrangler 项目里运行 PartyKit”列在未来规划中。这是该指南的能力边界说明,不能据此断言以后一定不会支持;在拿到可验证的配置和运行结果前,不应把 cloud-prem 写成现有 Worker 的一个普通依赖。PartyServer 的 README 则明确要求自行配置 Durable Object,并使用 Wrangler 原生 bindings。PartyKit cloud-prem 指南 · PartyServer README
从 PartyKit 迁移到 PartyServer 时,还要逐项核对 URL、身份验证、存储格式和房间 ID;不能只替换包名。
Cloudflare 技术栈里怎么组合
下图展示的是PartyServer 路径:普通 HTTP 与 WebSocket 可以进入同一个 Worker,再由它将实时连接路由到按房间 ID 划分的 Durable Object。图中的 D1、R2、Queues 是按业务需要增加的 Wrangler bindings,不是这个最小示例的依赖。
flowchart TB
C["浏览器 / 移动客户端"] --> W["Worker:认证、HTTP API、路由"]
W -->|"routePartykitRequest"| P["PartyServer:按 room ID 定位"]
P --> D["RoomServer Durable Object"]
D --> S["私有 SQLite / KV API"]
D -.->|"按需使用 binding"| E["D1 / R2 / Queues"]
W -.->|"按需使用 binding"| E
PartyServer 继承 DurableObject,没有另建数据库或运行时。 它补的是房间路由、连接生命周期和广播 API。房间内需要串行协调的状态可以放在该 Durable Object 的私有存储;跨房间列表、检索或报表通常还需要 D1 等可查询的数据层。后一句是基于各存储边界的工程设计建议,不是 PartyServer 自动提供的数据同步功能。PartyServer README · Durable Objects 存储
| 组件 | 放什么 | 不宜交给它做什么 |
|---|---|---|
| 入口 Worker | HTTP API、会话检查、WebSocket 握手路由 | 把普通无状态请求当作持续在线的房间实例 |
| PartyServer / Durable Object | 一个 room 内的连接、消息顺序、广播和私有持久存储 | 扫描所有 room 做全站列表、搜索或报表 |
| D1 / R2 / Queues(按需) | 可查询的业务事实、文件、可靠异步任务 | 代替 room 对当前连接做低延迟广播 |
这是工程职责划分,并非 PartyServer 自动提供的数据同步。需要从 Worker 触发房间事件时,通过 Durable Object binding 定位目标 room;如果选独立 PartyKit 服务,则调用其 HTTP 接口。避免把全站流量都集中到单个热点 room;一个 Durable Object 仍是一个协调边界。PartyKit storage 指南 · PartyServer README · Durable Objects 最佳实践
API Worker 与实时 Worker 分开部署时
如果由自己管理两个 Worker,Cloudflare 提供两种直接调用路径。Service bindings 让 API Worker 调用实时 Worker 的 fetch() 或其公开 RPC 方法;external Durable Object binding 则可在调用方配置 script_name 和 DO class_name,直接定位另一个 Worker 定义的 Durable Object。前者经过目标 Worker 的入口逻辑,后者直接取得指定 DO namespace;需要哪种边界,应按认证与部署职责决定。调用方与目标服务需满足 Cloudflare 对绑定账户、部署顺序和配置的要求。Service bindings · Wrangler Durable Objects bindings
这两种方式适合自行管理脚本和 class 名称的 PartyServer 工程。PartyKit cloud-prem 指南没有给出现有 Worker 可直接绑定其生成资源的稳定配置;在没有核实部署产物与官方支持边界前,按公开 HTTP / WebSocket 接口集成更稳妥。PartyKit cloud-prem 指南
TanStack 与房间服务怎样分工
这里的 TanStack 不是一个单独的实时后端。若应用使用 TanStack Start 部署在 Cloudflare Workers,可以把它当作提供页面、HTTP API 和服务端函数的入口;TanStack Router 管页面地址和 loader;可选的 TanStack Query 管可重新读取的服务端快照缓存。PartyKit 或 PartyServer / Durable Object 才持有房间连接并推送消息。即使前端只使用 Router 或 Query、没有 Start,这个分工也成立。Cloudflare 的 TanStack Start 指南 · Router Data Loading · Router 与 Query 集成
| 层 | 以 document-42 为例负责什么 | 数据更新时做什么 |
|---|---|---|
| TanStack Start / Worker(若采用) | 处理页面 SSR、身份校验、HTTP 快照与业务写入;服务端函数访问 Cloudflare bindings | 返回受权限保护的权威快照,写入成功后通知目标 room |
| TanStack Router | 从 /documents/$documentId 确定当前文档,loader 取得或预热首次数据 | 如果快照归 Router loader 管理,调用 router.invalidate() 重跑相关 loader |
| TanStack Query(可选) | 用 ['room', documentId] 等 key 缓存 HTTP 取得的文档快照 | 如果快照归 Query 管理,收到变更通知后对匹配 key 调用 queryClient.invalidateQueries() |
| 浏览器 WebSocket / PartySocket | 页面挂载后进入 room,接收低延迟事件,也可发送经授权的命令 | 重连后重新获取权威快照;自动重连本身不能补回断线期间的事件 |
| PartyServer / Durable Object 或独立 PartyKit room | 检查房间权限、协调消息、广播给同 room 客户端,按需持久化 | 持久业务变更成功后,广播包含 room ID / 版本的轻量通知 |
TanStack Router 与 Query 可以组合:loader 通过 queryClient.ensureQueryData() 预热,组件用 useSuspenseQuery() 消费同一份 Query 数据;这需要按官方 SSR 集成方式创建每请求独立的 QueryClient。若只用 Router loader,就失效 Router 数据;若使用 Query 缓存,就失效对应 Query key;只有两份数据都由页面使用时才需要两边一起更新。失效操作只触发前端重新读取,不会自动清除 CDN 缓存或修改数据库。Router 与 Query 集成 · TanStack Start 与 Query · Query Invalidation
从首次页面到实时更新
- 用户打开
/documents/document-42。Router loader 在需要时调用 Start server function,从 Worker 可以访问的持久数据层读取首次快照。有敏感数据或envbinding 的读取留在服务端;不要把秘钥塞进会在客户端导航时运行的 loader。TanStack Start Server Functions · Execution Model - 页面在浏览器 hydrate 后建立到
document-42room 的 WebSocket。React 的useEffect不在 SSR 时运行,适合在挂载时创建连接、卸载时关闭连接;也可使用partysocket/react的usePartySocket。HTTP 页面登录成功不代表 WebSocket 已授权,握手时仍须验证会话和该 room 的权限。React useEffect · PartySocket React 用法 · PartyServer 入口 hooks - 编辑写入走一条明确的权威路径:例如由 Start server function 校验后写入,再通知 room;或者命令直接进入 room,由 room 校验并写入自己的持久存储。不要让 HTTP 和 WebSocket 两条入口各自修改一份状态而没有一致性规则。
- 写入确认后,room 广播
{type: "room-changed", roomId: "document-42", version: 7}一类轻量通知。前端只使['room', 'document-42']失效并重取快照;如果快照是 Router loader 独自管理的,则调用router.invalidate()。这是建议的消息协议,前文纯广播示例没有实现 HTTP 快照或room-changed事件,不能直接把两段代码拼接当作完整应用。Query Invalidation · Router Data Mutations - 光标、打字中状态、在线成员这类高频短暂事件可以直接更新局部 React 状态;每条都重取 HTTP 快照会放大请求量。断线重连后再重取权威快照,并通过版本号识别遗漏;
PartySocket能自动重连与暂存待发送消息,但不会替应用补齐已经错过的业务事件。PartySocket API
如果 TanStack Start 与 PartyServer 在同一个 Worker,Cloudflare 官方指南允许用自定义 src/server.ts 导出 Durable Object class,Wrangler main 指向该文件。根据两个入口的公开 API,可先将 /parties/* 交给 routePartykitRequest(),其余请求交给 Start 的 handler.fetch;这是组合设计建议,本文没有对 TanStack Start 项目做端到端集成验收。实时路径绕过普通页面处理,需在 onBeforeConnect / onBeforeRequest 独立鉴权。如果 Start Worker 与实时 Worker 分开,Service binding 可供服务端调用另一 Worker,但浏览器 WebSocket 仍需要可访问的 URL 或明确的转发入口。部署路径变化不改变房间消息和快照的所有权。Cloudflare 的 TanStack Start 自定义入口 · PartyServer 路由 hooks · Service bindings
验收时至少检查:首屏能看到权限允许的快照;同一文档的两个客户端能接收变更,不同文档互不串消息;断线重连后状态可恢复;无权限的 HTTP 与 WebSocket 请求都被拒绝;光标消息不会触发整份文档重取。下一个最小示例只验证 Worker、DO 和广播链路,不包含上述完整应用的认证与快照接口。
最小可运行示例:一个 Worker 加一个 PartyServer room
以下示例是公开测试房间,用于确认 Worker 路由、Durable Object 和 WebSocket 广播的关系;它没有登录与写入权限控制,不应原样用于私有协作数据。示例按 2026-09-28 核对的 partyserver 仓库版本 0.5.10 编写;新建项目使用本机 Node.js/npm 与 Cloudflare 账户,本地运行只需前两者。PartyServer 的上游 README 仍展示旧式 migrations;这里按照 Cloudflare 当前文档为新 Durable Object class 使用声明式 exports 和 SQLite。PartyServer package.json · PartyServer README · Durable Object class exports
在一个新的、独立的项目目录中安装依赖(以下命令不会部署):
npm init -y
npm install partyserver@0.5.10
npm install --save-dev wrangler@4.131.0
mkdir -p src
src/index.js:
import { routePartykitRequest, Server } from "partyserver";
export class RoomServer extends Server {
static options = { hibernate: true };
onMessage(connection, message) {
if (typeof message !== "string" || message.length > 1024) {
connection.close(1009, "Message too large");
return;
}
this.broadcast(JSON.stringify({
room: this.name,
from: connection.id,
text: message,
}));
}
}
export default {
async fetch(request, env) {
if (new URL(request.url).pathname === "/health") {
return Response.json({ ok: true });
}
return (await routePartykitRequest(request, env)) ??
new Response("Not Found", { status: 404 });
},
};
wrangler.jsonc:
{
"name": "partyserver-room-demo",
"main": "src/index.js",
"compatibility_date": "2026-09-17",
"durable_objects": {
"bindings": [
{ "name": "RoomServer", "class_name": "RoomServer" }
]
},
"exports": {
"RoomServer": { "type": "durable-object", "storage": "sqlite" }
}
}
compatibility_date 使用 2026-09-17,因为本例固定的 Wrangler 4.131.0 内置本地 runtime 支持到该日期;把它直接写成文章日期 2026-09-28 会使 wrangler dev 启动失败。升级 Wrangler 后可再按其支持范围调整。
运行 npx wrangler dev,然后请求 http://127.0.0.1:8787/health,预期得到 {"ok":true}。在浏览器控制台运行以下 JavaScript;等待两个连接都打开后发送,两个连接应各收到包含 room: "demo" 与 text: "hello" 的消息。
const url = "ws://127.0.0.1:8787/parties/room-server/demo";
const sockets = [new WebSocket(url), new WebSocket(url)];
await Promise.all(sockets.map((socket) => new Promise((resolve) => {
socket.addEventListener("open", resolve, { once: true });
})));
sockets.forEach((socket, index) => {
socket.addEventListener("message", (event) => console.log(index, event.data));
});
sockets[0].send("hello");
routePartykitRequest() 将 /parties/room-server/demo 中的 room-server 匹配到 RoomServer binding,把 demo 当作房间名;RoomServer 的 this.name 就是该房间名。若把第二个 URL 的 demo 换成 other,它应收不到前一个 room 的广播。PartyServer 路由说明
本地实测(2026-09-28):使用上述固定依赖启动 Wrangler,GET /health 返回 {"ok":true};两个连接进入 demo 后各收到一条 hello 广播,进入 other 的第三个连接没有收到这条消息。WebSocket 检查使用本地 Node 客户端完成,未进行远端部署。
这个示例中的 hibernate: true 让空闲 WebSocket 连接能够由平台保持,而 RoomServer 实例可能重新创建;不要依赖 class 字段保存需要恢复的业务状态。真正需要持久化的房间状态写入 this.ctx.storage,并在唤醒时按需读取。原 PartyKit 平台也有 Hibernation,但开关写在实例的 options,不是 PartyServer 的 static options;两边设置不能混贴。PartyServer Hibernation · PartyKit Hibernation
若继续使用 PartyKit 平台
应用原本在 Worker、Pages、其他云或传统服务器上,都可以通过标准 HTTP / WebSocket 与独立的 PartyKit 项目通信。PartyKit 项目可以用 npx partykit@latest init、npx partykit dev 开发;发布到自有 Cloudflare 账户时,官方命令是 npx partykit deploy --domain <你的域名>,并要求在部署环境提供 CLOUDFLARE_ACCOUNT_ID 与 CLOUDFLARE_API_TOKEN。部署会改动远端资源,先在测试账户和域名完成验证,不把示例占位符当真实凭据。现有项目接入 · cloud-prem 指南
PartyKit 的 onBeforeConnect 与 onBeforeRequest 可分别拦截 WebSocket 握手和 HTTP 请求。浏览器若直连 PartyKit 域名,现有 Worker 对自己 API 的鉴权不会自动保护该连接;PartyKit 侧仍要验证会话和 room 权限。连接成功后还应验证每条消息的类型、大小与业务权限。PartyKit Authentication · Validating client inputs
PartyKit 官网在本次核对时把其托管 Individual 方案的 storage 描述为每 24 小时清空。因此需要长期保存的协作文档或审计数据,不应仅依据“有 room.storage API”就把托管免费环境当作持久数据库;先核对实际部署方案及其存储条款。这个官网价格与服务说明可能变化。PartyKit 官网 Pricing
选型时检查的边界
- 是否已有 Worker 与 Cloudflare bindings? 已有且希望共享部署与环境配置,优先从 PartyServer 入手;纯实时增量、应用可接受独立服务时,先试 PartyKit 平台。
- 状态归谁管理? 房间内顺序、连接和短期协同属于 Durable Object;需要跨房间查询的业务事实另设可查询存储,并明确更新与重试策略。
- 入口由谁认证? 对 HTTP 与 WebSocket 分别验证身份、room 访问权、消息大小与频率;示例的公开广播只适合本地验证。
- 是否真需要 PartyServer? 若只有一个计数器或既有 DO RPC,Cloudflare 原生 Durable Objects 就足够;PartyServer 的收益主要来自房间式 URL、WebSocket hooks 和广播封装。PartyServer 功能
相关站内阅读:Workers 运行时 API 与 Bindings · Durable Objects 使用方式与接口 · Queues 生产消费与重试 · TanStack Router 入门 · TanStack Query v5 基础 · TanStack Start 与 Workers Cache