Next.js + tRPC集成WebSockets订阅的实现方案问询
在Next.js中集成tRPC WebSocket订阅的正确方案
问题根因
你遇到的fs等Node.js库找不到的错误,本质是两个核心原因:
- Next.js的API路由是基于HTTP的无服务器短连接函数,不支持WebSocket长连接,无法直接在API路由内启动WebSocket服务;
- Next.js打包API路由时会使用浏览器兼容的配置,自动排除Node.js核心模块,而
ws库依赖这些模块,导致打包或运行时报错。
下面分两种部署场景给出解决方案:
场景1:自定义服务器部署(如Docker、EC2)
适合可以自行控制服务器环境的场景,直接通过自定义Node.js服务器集成tRPC WebSocket。
1. 安装依赖
npm install ws @trpc/server @trpc/client @types/ws # 或使用 yarn/pnpm
2. 创建自定义服务器文件
在项目根目录创建server.ts:
import { createServer } from 'http'; import { parse } from 'url'; import next from 'next'; import { applyWSSHandler } from '@trpc/server/adapters/ws'; // 导入你的tRPC路由和上下文 import { appRouter } from './server/trpc/router/_app'; import { createContext } from './server/trpc/context'; const isDev = process.env.NODE_ENV !== 'production'; const nextApp = next({ dev: isDev }); const nextRequestHandler = nextApp.getRequestHandler(); // 启动Next.js应用并绑定HTTP服务器 nextApp.prepare().then(() => { const httpServer = createServer((req, res) => { const parsedUrl = parse(req.url!, true); nextRequestHandler(req, res, parsedUrl); }); // 集成tRPC WebSocket服务 applyWSSHandler({ wss: new WebSocket.Server({ server: httpServer }), router: appRouter, createContext, }); const PORT = process.env.PORT || 3000; httpServer.listen(PORT, () => { console.log(`服务运行在 http://localhost:${PORT}`); console.log(`WebSocket服务已就绪`); }); });
3. 修改启动脚本
更新package.json的scripts字段,用自定义服务器替代默认的Next.js启动命令:
{ "scripts": { "dev": "node server.ts", "build": "next build", "start": "NODE_ENV=production node server.ts" } }
4. 客户端配置WebSocket链接
在tRPC Provider中同时配置HTTP(用于普通请求)和WebSocket(用于订阅)链接:
import { createTRPCReact } from '@trpc/react-query'; import { httpBatchLink, wsLink, createWSClient } from '@trpc/client'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import type { AppRouter } from '../server/trpc/router/_app'; export const trpc = createTRPCReact<AppRouter>(); const queryClient = new QueryClient(); export function TRPCProvider({ children }: { children: React.ReactNode }) { const wsClient = createWSClient({ url: process.env.NEXT_PUBLIC_WS_URL || 'ws://localhost:3000', }); const trpcClient = trpc.createClient({ links: [ wsLink({ client: wsClient }), httpBatchLink({ url: '/api/trpc' }), ], }); return ( <QueryClientProvider client={queryClient}> <trpc.Provider client={trpcClient} queryClient={queryClient}> {children} </trpc.Provider> </QueryClientProvider> ); }
场景2:Vercel无服务器部署
Vercel的无服务器函数不支持WebSocket长连接,建议用**Server-Sent Events (SSE)**替代WebSocket实现tRPC订阅:
1. 配置tRPC路由的订阅端点
// server/trpc/router/notification.ts import { z } from 'zod'; import { publicProcedure, router } from '../trpc'; import { Observable } from '@trpc/server/observable'; export const notificationRouter = router({ subscribeToUpdates: publicProcedure.subscription(() => { return new Observable((emit) => { // 模拟实时推送逻辑,替换为你的业务代码 const interval = setInterval(() => { emit.next({ content: '新消息通知' }); }, 5000); // 清理函数:客户端取消订阅时触发 return () => clearInterval(interval); }); }), });
2. 客户端配置SSE链接
替换WebSocket链接为SSE链接,同时保留HTTP链接:
import { createTRPCReact } from '@trpc/react-query'; import { httpBatchLink, sseLink } from '@trpc/client'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import type { AppRouter } from '../server/trpc/router/_app'; export const trpc = createTRPCReact<AppRouter>(); const queryClient = new QueryClient(); export function TRPCProvider({ children }: { children: React.ReactNode }) { const trpcClient = trpc.createClient({ links: [ sseLink({ url: '/api/trpc' }), httpBatchLink({ url: '/api/trpc' }), ], }); return ( <QueryClientProvider client={queryClient}> <trpc.Provider client={trpcClient} queryClient={queryClient}> {children} </trpc.Provider> </QueryClientProvider> ); }
内容的提问来源于stack exchange,提问作者Lukas Thiersch
相关产品推荐
相关产品推荐

