在 Next.js 中为 tRPC WebSocket 接入 MessagePack 二进制编码next-websockets-encoder 示例全解析【免费下载链接】trpc♀️ Move Fast and Break Nothing. End-to-end typesafe APIs made easy.项目地址: https://gitcode.com/GitHub_Trending/tr/trpc本示例examples/next-websockets-encoder演示了如何基于 Next.js 15 Pages Router 与 tRPC v11 构建 WebSocket 订阅服务并通过experimental_encoder将默认的 JSON 文本协议替换为MessagePack 二进制编码从而在实时订阅场景中获得更小的网络载荷与更优的传输效率。读完本文你将掌握experimental_encoder在服务端与客户端两侧的接线方式、MessagePack 与 JSON 在undefined语义上的差异及规避技巧、开发/生产两种 WebSocket 服务形态以及如何在浏览器 DevTools 中直接验证二进制帧。示例概览技术栈与功能点该示例是一套可独立运行的 Next.js 应用其 package.json 中明确了完整技术栈Next.js 15Pages Router提供 HTTP 页面与/api/trpc接口tRPC 客户端三件套trpc/client、trpc/next、trpc/react-query配合tanstack/react-query驱动查询/订阅状态trpc/server提供服务端路由与 WebSocket 适配器trpc/server/adapters/wsws库托管独立 WebSocket 服务msgpack/msgpack提供 MessagePack 二进制编解码zod做输入校验Tailwind CSS负责界面样式。它覆盖了三条 RPC 路径正好可以对比 HTTP 与 WS 两条通道过程类型说明传输通道healthcheckquery返回yay!无输入HTTPgreetquery接收{ name: string }返回问候语HTTPrandomNumbersubscription每秒推送一个随机数页面实时渲染WebSocket MessagePack相关定义见 routers/_app.ts其中订阅用observable封装setInterval并在清理函数中clearInterval是 tRPC 订阅的标准写法。核心机制experimental_encoder是什么tRPC WebSocket 链路默认使用 JSON 传输文本帧。experimental_encoder是客户端与服务端 WebSocket 适配器共同暴露的扩展点允许你注入自定义的双向编解码器在连接建立后对每一条消息执行编码发送方与解码接收方。该能力目前仍标记为experimental使用时两端必须配套配置同一份编码器否则解码端会收到无法解析的数据。在示例中编码器被注入到两处// 服务端src/server/wssDevServer.ts applyWSSHandler({ wss, router: appRouter, experimental_encoder: msgpackEncoder, }); // 客户端src/utils/trpc.ts createWSClient({ url: WS_URL, experimental_encoder: msgpackEncoder, });从源码看服务端通过applyWSSHandler创建处理器并传入 encoder见 wssDevServer.ts客户端则是在createWSClient中注入见 utils/trpc.ts。编码逻辑集中在 utils/encoder.ts两端共享同一份实现确保编解码约定一致。手写一个 MessagePack EncodertRPC 的Encoder类型要求提供一对encode/decode方法。示例实现如下源码位于 utils/encoder.tsimport { decode, encode } from msgpack/msgpack; import type { Encoder } from trpc/client; // MessagePack converts undefined to null, but tRPC expects undefined for optional fields. // Strip undefined values before encoding to match JSONs behavior. function stripUndefinedT(value: T): T { if (value null || typeof value ! object) return value; if (Array.isArray(value)) return value.map(stripUndefined) as T; const result: Recordstring, unknown {}; for (const [k, v] of Object.entries(value)) { if (v ! undefined) result[k] stripUndefined(v); } return result as T; } export const msgpackEncoder: Encoder { encode: (data) encode(stripUndefined(data)), decode: (data) { if (typeof data string) { throw new Error( msgpackEncoder expected binary data but received a string., ); } return decode(data instanceof ArrayBuffer ? new Uint8Array(data) : data); }, };对encode与decode逐一拆解encode 方向先把数据交给stripUndefined递归清洗再交给msgpack/msgpack的encode产出二进制。这里之所以要递归剥掉undefined是因为 MessagePack 协议中没有undefined类型它会把undefined转成null而 tRPC 依赖字段缺失undefined来区分可选字段未提供与显式置空两者语义不同。JSON 序列化天然丢弃undefined键因此清洗函数的作用就是让二进制编码对齐 JSON 的既有行为避免可选参数在反序列化后被错误地变成null。decode 方向入参可能是string、ArrayBuffer或Uint8Array。若收到字符串则直接抛错——这通常是一端配了编码器、另一端没配时才会出现的症状错误信息会提示你检查两端是否都设置了experimental_encoder。若是ArrayBuffer则先包成Uint8Array再交给decode还原对象。客户端接线HTTP 与 WS 双链路共存客户端的核心在 utils/trpc.ts。它定义了两个固定地址const WS_URL ws://localhost:3001; const APP_URL http://localhost:3000;getEndingLink是链路选择的关键SSR/服务端渲染阶段走 HTTP浏览器端走 WebSocket。这样页面首屏数据可由 HTTP 完成订阅等实时更新则在客户端通过 WS 长连接推送。function getEndingLink(ctx: NextPageContext | undefined): TRPCLinkAppRouter { if (typeof window undefined) { return httpBatchLink({ url: ${APP_URL}/api/trpc, headers() { if (!ctx?.req?.headers) { return {}; } return { ...ctx.req.headers, x-ssr: 1 }; }, }); } const client createWSClient({ url: WS_URL, experimental_encoder: msgpackEncoder, }); return wsLink({ client }); }客户端在 SSR 时把req.headers原样透传给 HTTP 链路并标记x-ssr: 1这在服务端认证等场景很常见在浏览器端则创建带 MessagePack 编码器的 WS 客户端。整体客户端通过createTRPCNext配置启用了ssr: true与ssrPrepass链路顺序为loggerLink开发期打印请求日志→ 上述终止链路最终由trpc.withTRPC(MyApp)包裹应用见 _app.tsx。HTTP 查询与 WS 订阅因此可以并存页面用trpc.greet.useQuery({ name })走 HTTP 拿到问候语用trpc.randomNumber.useSubscription(...)走 WS 接收每秒推送的随机数见 pages/index.tsx。由于浏览器端的 WS 客户端已带 MessagePack 编码器订阅数据以二进制帧到达再经decode还原后交给 React 状态渲染。服务端两种形态独立 WS 服务与单端口集成服务端对 WebSocket 的处理分开发与生产两种形态均复用同一个appRouter与同一份msgpackEncoder。开发形态wssDevServer.ts独立启动一个监听3001端口的WebSocketServer与 Next 应用3000分开运行因此页面脚本中把WS_URL指向ws://localhost:3001。它额外在connection/close事件里打印在线连接数并在收到SIGTERM时调用handler.broadcastReconnectNotification()通知所有客户端主动重连再关闭服务。生产形态prodServer.ts用 Nodehttp.createServer承载 Next 请求处理并把WebSocketServer({ server })挂到同一个 HTTP 服务器上实现 HTTP 与 WS 共用 80/443 端口的单端口部署同时保留server.on(upgrade)的手动升级处理。若需在真实环境中优雅退出同样通过broadcastReconnectNotification()让客户端感知服务端下线。两条服务端路径配置experimental_encoder: msgpackEncoder的方式完全一致见 prodServer.ts。可见该选项位于 WebSocket 适配器层与路由无关无需修改任何 procedure 或 router 定义即可整体切换编码格式。运行与验证仓库采用 pnpm workspace进入示例目录执行pnpm install pnpm devdev脚本实际并行运行两件事见 package.jsondev:wsscross-env PORT3001 tsx watch src/server/wssDevServer.ts在 3001 端口启动带 MessagePack 编码的 WebSocket 服务dev:nextnext dev在 3000 端口启动 Next.js 应用。随后打开http://localhost:3000页面上有三个区块Health CheckHTTP与GreetingHTTP分别展示查询结果可在输入框实时改名字看问候语更新Random Number Subscription每秒收到一个随机数并实时刷新页面同时展示订阅status与最近 10 条历史数据便于观察持续推送页面右下角标注当前序列化库为msgpack/msgpack。要直观确认二进制传输打开浏览器DevTools → Network → WS选中连接后查看帧内容即可看到 MessagePack 二进制帧而非普通 JSON 文本。若浏览器端意外收到字符串帧说明客户端与服务端的experimental_encoder配置不一致decode中专门为这种情况准备的报错会立即提示你排查两端配置。除此之外package.json还提供了生产构建入口build会先以NODE_ENVproduction执行next build再用tsc --project tsconfig.server.json把服务端编译到dist/server之后用start运行编译产物由 prodServer.ts 在单端口上同时承载页面与 WebSocket。实践要点小结两端必须配套配置experimental_encoder只在 WebSocket 通道生效服务端applyWSSHandler与客户端createWSClient需传入同一个编码器缺一端会导致对方收到无法解析的帧处理undefined语义差异MessagePack 没有undefined需像示例的stripUndefined一样递归剔除后再编码否则可选字段会退化为null破坏 tRPC 对可选输入的判定逻辑区分通道与形态SSR 走httpBatchLink、浏览器走wsLink是 Pages Router 下兼顾首屏与实时的常见布局独立 WS 端口开发与单端口集成生产两种服务端写法分别应对不同部署环境验证要看帧仅在 Network 面板查看 WS 帧才能确认二进制编码真正生效本示例 routers/_app.ts、encoder.ts 与 trpc.ts 是理解这条链路最直接的三个入口。【免费下载链接】trpc♀️ Move Fast and Break Nothing. End-to-end typesafe APIs made easy.项目地址: https://gitcode.com/GitHub_Trending/tr/trpc创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考