ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

用 PartyKit 为 Next.js 应用添加实时功能:PartyPoll 实时投票应用从零到部署实战教程

用 PartyKit 为 Next.js 应用添加实时功能:PartyPoll 实时投票应用从零到部署实战教程 后端【免费下载链接】partykitPartyKit simplifies developing multiplayer applications项目地址https://gitcode.com/gh_mirrors/pa/partykit点击查看免费下载本篇实战教程基于 PartyKit 官方入门教程对应仓库apps/docs/src/content/docs/tutorials/add-partykit-to-a-nextjs-app/下 7 篇文档完整演示如何把一个普通的 Next.js 应用升级为具备实时多人协作能力的投票应用 PartyPoll用户创建投票后即可分享链接所有访问者通过 WebSocket 实时看到票数变化投票数据借助 PartyKit 房间内置的键值存储实现持久化。读完本文你将掌握partykit init / dev / deploy三大命令、Party.Server的onRequest / onMessage / onStart生命周期、usePartySocketReact Hook以及HTTP 创建房间 WebSocket 实时广播的经典架构。一、教程概览我们要构建什么这个教程的最终产物是一个包含两个页面的投票应用第一个页面一个表单用于生成投票。用户输入投票标题与若干选项点击提交后创建一场新投票。第二个页面展示新创建的投票用户可以投票也可以把链接分享给朋友所有访问者看到的票数都会实时同步。配套的完整代码分别以初始模板和成品代码两个仓库存在教程每一步都会给出对应的成品代码片段供对照。本文以官方文档为主体结合仓库源码对每一步的原理做纵深剖析。实时功能是如何运作的当用户提交表单时会发生两件事HTTP 请求创建房间表单向 PartyKit 发起一个 HTTPPOST请求创建一个新的 PartyKit 房间room该房间将专门服务这场新投票WebSocket 连接实时同步表单随后重定向到投票页面浏览器向该 PartyKit 房间发起一条新的 WebSocket 连接。投票创建完成的那一刻其链接即可分享。新用户访问投票页面时同样会建立 WebSocket 连接所有人的投票结果会实时广播给其他在线用户无需刷新页面。这种每个业务实体对应一个独立房间的建模方式是 PartyKit 应用的核心设计模式房间既是隔离的边界也是实时同步的最小单元。二、快速开始跑起初始项目教程提供了一个精简版barebone应用作为起点。获取并启动它只需四步# 1. 克隆初始应用代码tutorial-starter-partypoll git clone starter-repo partypoll cd partypoll # 2. 安装依赖 npm install # 3. 启动 Next.js 开发服务器 npm run dev # 4. 浏览器打开 http://localhost:3000/之后每次保存文件页面都会自动热重载。这个初始应用已经包含生成投票的表单页面、渲染投票的PollUI组件、服务端 action 中读取表单数据的代码骨架以及一份定义环境变量的.env.example。我们的工作就是在此基础上接入 PartyKit。三、第一步初始化 PartyKit 并启动开发服务器安装 PartyKit在应用目录的终端中运行npx partykitlatest init这条命令会往你的项目里添加三样东西partykit包用于构建和部署实时服务器的核心 SDK仓库中对应的包源码位于 packages/partykitbin入口指向dist/bin.mjs可查看其 package.jsonpartysocket包提供 WebSocket 客户端连接断开时自动重连对应 packages/partysocket仓库中的 README 与 API 文档 有完整说明一个新的party目录你的服务端代码将存放在这里。init还会创建partykit.json配置文件。从仓库中的 init 模板 可以看到默认生成的服务器是一个实现了Party.Server接口的类自带onConnect连接建立时向客户端发送问候语和onMessage将收到的消息广播给房间内其他连接两个方法的雏形——这正是我们后续改造的起点。认识 partykit.jsoninit生成的配置文件是项目行为的核心。结合 partykit.json 配置参考与本教程最相关的字段如下{ name: partypoll, // 项目名决定线上地址前缀 main: party/index.ts, // 服务端入口文件 port: 1999 // dev 服务器端口默认 1999 // persist: .partykit/state // dev 模式下存储持久化路径默认 .partykit/state }其中name会用于生成线上 URL典型格式https://项目名.GitHub用户名.partykit.devmain指定Party.Server默认导出的位置port默认是1999本地开发时客户端需要连到localhost:1999。运行 PartyKit 开发服务器启动 Next.js dev server 之后另开一个终端标签页运行npx partykit dev如果一切正常你会看到类似下面的输出表示 PartyKit 开发服务器已在localhost:1999就绪并开始监听代码变更dev会监视main字段指向的入口文件也可用npx partykit dev src/server.ts显式指定入口代码一保存即自动重启服务器与 Next.js 的热重载体验一致。本地开发阶段的 PartyKit 地址就是http://localhost:1999dev模式下房间存储会默认持久化到.partykit/state可通过persist字段自定义或设为false关闭。四、第二步编写 PartyKit 服务器处理 HTTP 请求初始化后party目录下会有一个index.ts服务端模板。将其内容替换为import type * as Party from partykit/server; import type { Poll } from /app/types; export default class Server implements Party.Server { constructor(readonly room: Party.Room) {} poll: Poll | undefined; async onRequest(req: Party.Request) { if (req.method POST) { const poll (await req.json()) as Poll; this.poll { ...poll, votes: poll.options.map(() 0) }; } if (this.poll) { return new Response(JSON.stringify(this.poll), { status: 200, headers: { Content-Type: application/json } }); } return new Response(Not found, { status: 404 }); } } Server satisfies Party.Worker;逐段拆解这段代码房间内内存状态。因为每场投票对应一个独立房间投票数据可以安全地保存在内存中无需共享数据库poll: Poll | undefined;甚至可以直接复用 Next.js 应用里定义的 TypeScript 类型Poll前后端共享同一份类型契约。onRequest处理 HTTP 请求。onRequest是Party.Server的核心生命周期方法之一收到的是标准 Fetch API 风格的Party.Request需要返回标准Response详见 Party.Server API 参考if (req.method POST) { const poll (await req.json()) as Poll; this.poll { ...poll, votes: poll.options.map(() 0) }; } if (this.poll) { return new Response(JSON.stringify(this.poll), { status: 200, headers: { Content-Type: application/json }, }); }这里的关键背景是投票将在 Next.js 的 server action 中创建而 Server Component 无法建立 WebSocket 连接。好在 PartyKit 房间同样接受 HTTP 请求——用户点击提交按钮时表单会向 PartyKit 服务器发送POST请求服务器解析 JSON 载荷将每个选项的初始票数初始化为0一场新投票就此诞生。兜底响应。如果房间内还没有投票数据则返回 404return new Response(Not found, { status: 404 });从源码看 Party.Server 的完整生命周期对照仓库中的 Party.Server API 参考这个类可用的生命周期方法远不止onRequest方法触发时机本教程中的用途onStart服务器启动或从休眠唤醒后、首次连接/请求前从存储加载投票数据onConnect新的 WebSocket 连接建立模板自带示例onMessage收到客户端 WebSocket 消息处理投票事件并广播onClose/onError连接关闭 / 连接出错清理与错误处理onRequest房间 URL 收到 HTTP 请求创建投票 / 返回投票数据构造函数接收的Party.Room实例提供id、storage、broadcast、getConnections()等资源。最后一行Server satisfies Party.Worker;用于对静态方法如onFetch做类型校验在纯实例方法场景下同样是一个良好的类型实践。五、第三步把 Next.js 表单与 PartyKit 服务器连接起来服务器就绪后下一步是让 UI 与服务器通信。当用户提交投票表单时Next.js 应用要把数据发给 PartyKit 服务器。连接创建投票的表单在app目录的page.tsx中初始代码已经在 server action 里帮你写好了从表单提取数据的逻辑const title formData.get(title)?.toString() ?? Anonymous poll; const options: string[] []; for (const [key, value] of formData.entries()) { if (key.startsWith(option-) value.length 0) { options.push(value.toString()); } } const id randomId(); const poll: Poll { title, options };现在需要把这些数据通过 HTTP 请求发给 PartyKitawait fetch(${PARTYKIT_URL}/party/${id}, { method: POST, body: JSON.stringify(poll), headers: { Content-Type: application/json } });示例项目中已经预定义了PARTYKIT_URL变量其值默认为 PartyKit 开发服务器地址即http://localhost:1999。教程最后一步部署时会把该变量替换为线上项目地址。要点新 id 即新房间请注意代码中随机生成的id。每个新的id都会创建一个全新的 PartyKit 房间也就是一个独立的 PartyKit 服务器实例这意味着每场投票都拥有自己专属的房间——投票之间天然隔离不会相互干扰。此时可以打开浏览器创建一场投票验证效果——数据能够创建成功但页面上的数据仍是静态的接下来解决实时问题。连接展示投票的页面在app/[poll_id]目录的page.tsx中利用上一节onRequest方法返回的数据在服务端渲染投票const req await fetch(${PARTYKIT_URL}/party/${pollId}, { method: GET, next: { revalidate: 0 } }); if (!req.ok) { if (req.status 404) { notFound(); } else { throw new Error(Something went wrong.); } }要点禁用 Next.js 缓存注意revalidate: 0。Next.js 默认会缓存服务端fetch的结果而投票数必须始终是最新的因此这里显式禁用缓存。应用性能依然有保障——因为 PartyKit 与 Vercel 运行在同一个边缘网络edge network上跨服务的数据往返延迟很低。最后解析响应并替换掉页面里的模拟数据const poll (await req.json()) as Poll;到这里表单创建的数据已经能在投票页面正确渲染了。但请注意此刻的渲染仍是一次性的——用户投票后其他用户看不到变化除非手动刷新。这正是下一步 WebSocket 要解决的问题。六、第四步接入 WebSocket 实时连接用 usePartySocket 建立连接打开渲染投票的PollUI组件文件先取消注释文件顶部的导入语句启用partysocket包import usePartySocket from partysocket/react;因为PollUI是客户端组件非 Server ComponentWebSocket 连接将直接从用户设备发起。在 state setter 与sendVote方法之间加上这个 Hookconst socket usePartySocket({ host: PARTYKIT_HOST, room: id, onMessage(event) { const message JSON.parse(event.data) as Poll; if (message.votes) { setVotes(message.votes); } } });usePartySocket是 PartyKit 随partysocket包提供的 React Hook封装了 WebSocket 的全部生命周期管理组件挂载时自动连接、卸载时自动断开、事件处理器自动清理。它接受与PartySocket相同的参数另支持onOpen、onMessage、onClose、onError生命周期回调完整 API 见 PartySocket API 文档。其中id是PollUI的 prop与创建投票时随机生成的poll id相同——WebSocket 正是靠它连接到正确的房间。host在开发环境为localhost:1999线上为项目名.用户名.partykit.dev这样的域名对应PARTYKIT_HOST环境变量。onMessage回调在收到 PartyKit 服务器发来的 WebSocket 消息时触发。这里解析消息并更新本地组件状态setVotes从而驱动 UI 刷新——整个实时链路的核心逻辑就在这短短几行。点击按钮发送投票连接建立后修改已有的sendVote函数让按钮把选中的选项发给服务器const sendVote (option: number) { if (vote null) { socket.send(JSON.stringify({ type: vote, option })); setVote(option); } };vote null的守卫确保每个用户只能投一次票本教程刻意省略了认证后续会讨论。通过socket.send发送的是一条 JSON 消息携带事件类型vote与被选中的选项下标。PartySocket不只是普通的 WebSocket值得展开的是partysocket并非简单的WebSocket包装。其 API 文档 列出的核心能力包括与浏览器 WebSocket API 完全兼容Level0 与 Level2 事件模型断线自动重连连接意外关闭后自动恢复消息缓冲Buffering重连期间发送的消息会被暂存连接恢复后自动补发多平台支持Web、Service Worker、Node.js、React Native 均可使用可配置重连策略如maxRetries最大重试次数默认Infinity、minReconnectionDelay最小重连延迟默认1000 Math.random() * 4000毫秒、connectionTimeout连接超时默认4000毫秒等。这些能力对真实世界的弱网、掉线场景至关重要——投票应用在用户切换网络时不会丢失同步。七、第五步服务端广播让票数实时可见现在客户端已能实时记录投票但其他用户还看不到变化除非刷新页面。这一步让服务器把变化立即广播给所有已连接客户端。回到服务端代码party/index.ts。目前服务器能接收 WebSocket 消息却还没有处理它们。添加onMessage方法async onMessage(message: string) { if (!this.poll) return; const event JSON.parse(message); if (event.type vote) { this.poll.votes![event.option] 1; this.room.broadcast(JSON.stringify(this.poll)); } }逻辑非常清晰解析客户端发来的消息 → 识别vote事件 → 对应选项票数1→ 用room.broadcast把更新后的投票对象广播给房间内所有连接。所有在线客户端收到后onMessage回调会更新本地状态票数随之实时跳动。验证方法在两个或更多标签页里打开同一个投票页面在其中一页投票观察另一页是否即时变化。从源码看room.broadcast它向房间内所有已连接客户端发送消息可选第二个参数为排除名单例如this.room.broadcast(message, [sender.id])可跳过消息发送者自己详见 Party.Server API 参考 中Room.broadcast一节。八、第六步用房间存储持久化投票数据PartyKit 房间自带键值存储key-value storage无需外部数据库即可持久化数据它同时保证了在服务器重启或应用重新部署等场景下数据不丢失。本步为服务器开启投票持久化。启动时加载数据在服务端文件party/index.ts中新增onStart方法。它会在房间迎来第一个连接时或服务器重启后被触发async onStart() { this.poll await this.room.storage.getPoll(poll); }持久化数据再添加一个辅助方法把数据写入存储async savePoll() { if (this.poll) { await this.room.storage.putPoll(poll, this.poll); } }然后在onRequest中调用它投票创建时保存只需加一行async onRequest(req: Party.Request) { if (req.method POST) { const poll (await req.json()) as Poll; this.poll { ...poll, votes: poll.options.map(() 0) }; // ADD THIS LINE: this.savePoll(); } if (this.poll) { return new Response(JSON.stringify(this.poll), { status: 200, headers: { Content-Type: application/json }, }); } return new Response(Not found, { status: 404 }); }同样在onMessage中调用它用户投票时保存最新票数async onMessage(message: string) { if (!this.poll) return; const event JSON.parse(message); if (event.type vote) { this.poll.votes![event.option] 1; this.room.broadcast(JSON.stringify(this.poll)); // ADD THIS LINE: this.savePoll(); } }完成至此投票数据已持久化在 PartyKit 服务器上——即使服务器重启、重新部署投票与票数都能从存储中恢复。Storage API 的关键约束结合仓库中 Persisting state into storage 指南使用Room.storage时有几个值得注意的边界Key必须是字符串最大2,048 字节Value可以是任意支持 结构化克隆算法 的类型单个值上限128 KiB131,072 字节每房间总计128 MiB内存建议避免一次性把海量数据读入内存常用操作storage.getType(key)读、storage.put(key, value)写、storage.delete(key)删、storage.list()列出全部条目。本教程采用的在onStart中一次性读入内存是文档推荐的典型模式特别适合读多写少或需要把多份数据合并成派生数据的场景指南 中还有按需读取的替代模式。注意onStart完成前房间不会处理任何连接或请求因此加载逻辑是安全的。九、第七步部署上线PartyKit 托管实时服务器但网站本身可以托管在任意你选择的部署平台上。客户端与 PartyKit 服务器无需分属不同仓库但需要分别部署。部署 PartyKit 服务器在项目目录中运行npx partykit deploy首次部署时CLI 会引导你用 GitHub 账号登录自动打开浏览器完成授权随后应用会被部署到你的partykit.dev域名上地址遵循模式[你的项目名].[你的 GitHub 用户名].partykit.dev部署完成后你会拿到这个 URL——部署 Next.js 应用时需要用到它。域名分配最长可能需要两分钟。详细的部署与调试流程可参考仓库中的 Deploying your PartyKit server 指南其中还包含用npx partykit tail实时查看线上日志排查问题的方法。部署 Next.js 应用这个 Next.js 应用通常部署到 Vercel。部署时务必记得设置环境变量例如NEXT_PUBLIC_PARTYKIT_HOSTpartypoll.你的用户名.partykit.dev PARTYKIT_URLhttps://partypoll.你的用户名.partykit.devPARTYKIT_URL供 Next.js 服务端server action、Server Component发起 HTTPfetch使用NEXT_PUBLIC_PARTYKIT_HOST即客户端usePartySocket中的PARTYKIT_HOST供浏览器建立 WebSocket 连接使用。示例仓库中已包含一份.env.example文件列出了你需要设置的全部环境变量可直接参照配置。教程之外关于认证细心的读者会发现目前用户可以重复投票多次。为保持教程篇幅精简官方刻意省略了**认证authentication**环节。若要限制每人一票通常的做法是在客户端 WebSocket 连接时携带认证信息PartySocket支持query参数注入 token在服务端用getConnectionTags给连接打标签或结合connection.setState存储用户身份详见 Party.Server API 参考更完整的思路可参考仓库中 authentication 指南 与 validating-client-inputs 指南。十、总结一套可复用的实时应用骨架回顾整个教程你实际掌握的是一套可复用于任何实时应用的完整骨架建模每个业务实体投票、聊天室、文档、游戏局对应一个 PartyKit 房间创建HTTPPOST到/party/{id}创建房间并初始化状态读取HTTPGET注意禁用缓存或 WebSocket 连接获取状态实时客户端usePartySocket建立连接服务端onMessage处理消息、room.broadcast广播变更持久化onStart从room.storage加载、写操作时storage.put保存部署npx partykit deploy部署实时服务器网站部署到任意平台并配置环境变量。在本仓库中你还可以进一步探索examples/目录下有大量可运行的示例basic、ai、react、yjs 等每个都自带partykit.json与完整的server.ts/client.tsPartyKit 参考文档 汇总了服务端、客户端与 CLI 的全部 APIparty.io 与 partymix 等包则展示了 PartyKit 在 Socket.IO 兼容与数据加密同步等方向的延伸能力。从这场酱馅饺子是不是墨西哥馅饼Are pierogi an empanada?的投票开始你的实时应用之旅才刚刚启程。赞分享后端【免费下载链接】partykitPartyKit simplifies developing multiplayer applications项目地址https://gitcode.com/gh_mirrors/pa/partykit点击查看免费下载相关推荐PartyKit 实时广播实战在 Next.js 投票应用中用 onMessage 与 room.broadcast 推送投票变更PartyKit 实时广播实战在 Next.js 投票应用中用 onMessage 与 room.broadcast 推送投票变更 本篇是「将 PartyKi后端用 Next.js PartyKit 构建实时投票应用Live Polls完整指南用 Next.js PartyKit 构建实时投票应用Live Polls完整指南 本指南以 PartyKit 官方示例「Live pollsNext后端将 PartyKit 实时服务器与 Next.js 应用分别部署从 partykit deploy 到 Vercel 环境变量配置将 PartyKit 实时服务器与 Next.js 应用分别部署从 partykit deploy 到 Vercel 环境变量配置 PartyKit 负责托管后端上一篇TranslumoWindows平台实时屏幕翻译终极指南5分钟上手跨越语言障碍下一篇如何用KMS智能激活工具一键搞定Windows和Office永久激活新手必看完整指南创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进