跳至正文
Cloudflare — PartyKit 与 PartyServer:能力、场景及 Cloudflare / TanStack 集成

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 effort ultra,执行入口 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 工程如何接入。

英文术语中文名称简要解释
PartyKitPartyKit提供房间服务 API、CLI 和部署平台的项目;其 room 由 Durable Object 承载。
PartyPartyKit 服务类型URL /parties/main/document-42 中的 main,同一项目可以定义多个。
room实时房间由 document-42 一类业务 ID 寻址的隔离实例。
Durable Object持久化对象Cloudflare 中按对象 ID 定位、集中协调请求并持有私有存储的运行实例。
PartyServerPartyServerCloudflare 维护的 Durable Objects 库,为房间路由、WebSocket 生命周期和广播提供封装。
PartySocketPartySocket可选的 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 为例,运行过程可以拆成四步:

  1. 两个浏览器连接 /parties/main/document-42。main 选择服务类型,document-42 选择 room;同一 ID 的连接进入同一个逻辑房间,另一个文档 ID 则进入另一房间。多 Party 路由
  2. room 在 WebSocket 握手和收到消息时检查身份、文档权限与消息内容;通过检查的修改被广播给当前房间的其他连接。
  3. 如果修改必须在断线后仍然存在,room 把权威状态或快照写进持久存储;新连接可通过 HTTP 或连接后的消息取得快照,再继续接收增量事件。
  4. 空闲休眠、实例重建或重新部署后,从持久存储恢复。不能把 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 开发模式
PartyServerpartyserver 作为依赖放进自己的 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 存储

组件放什么不宜交给它做什么
入口 WorkerHTTP 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

从首次页面到实时更新

  1. 用户打开 /documents/document-42。Router loader 在需要时调用 Start server function,从 Worker 可以访问的持久数据层读取首次快照。有敏感数据或 env binding 的读取留在服务端;不要把秘钥塞进会在客户端导航时运行的 loader。TanStack Start Server Functions · Execution Model
  2. 页面在浏览器 hydrate 后建立到 document-42 room 的 WebSocket。React 的 useEffect 不在 SSR 时运行,适合在挂载时创建连接、卸载时关闭连接;也可使用 partysocket/react 的 usePartySocket。HTTP 页面登录成功不代表 WebSocket 已授权,握手时仍须验证会话和该 room 的权限。React useEffect · PartySocket React 用法 · PartyServer 入口 hooks
  3. 编辑写入走一条明确的权威路径:例如由 Start server function 校验后写入,再通知 room;或者命令直接进入 room,由 room 校验并写入自己的持久存储。不要让 HTTP 和 WebSocket 两条入口各自修改一份状态而没有一致性规则。
  4. 写入确认后,room 广播 {type: "room-changed", roomId: "document-42", version: 7} 一类轻量通知。前端只使 ['room', 'document-42'] 失效并重取快照;如果快照是 Router loader 独自管理的,则调用 router.invalidate()。这是建议的消息协议,前文纯广播示例没有实现 HTTP 快照或 room-changed 事件,不能直接把两段代码拼接当作完整应用。Query Invalidation · Router Data Mutations
  5. 光标、打字中状态、在线成员这类高频短暂事件可以直接更新局部 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

在一个新的、独立的项目目录中安装依赖(以下命令不会部署):

sh
npm init -y
npm install partyserver@0.5.10
npm install --save-dev wrangler@4.131.0
mkdir -p src

src/index.js:

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:

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" 的消息。

js
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

选型时检查的边界

  1. 是否已有 Worker 与 Cloudflare bindings? 已有且希望共享部署与环境配置,优先从 PartyServer 入手;纯实时增量、应用可接受独立服务时,先试 PartyKit 平台。
  2. 状态归谁管理? 房间内顺序、连接和短期协同属于 Durable Object;需要跨房间查询的业务事实另设可查询存储,并明确更新与重试策略。
  3. 入口由谁认证? 对 HTTP 与 WebSocket 分别验证身份、room 访问权、消息大小与频率;示例的公开广播只适合本地验证。
  4. 是否真需要 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

博客助手

正在打开博客助手…