Vercel部署的NextJS应用中Server-Sent Events无法运行求助
解决Vercel部署NextJS流式聊天接口504超时问题
问题分析
本地运行正常但Vercel生产环境出现504,核心原因是Vercel Serverless Functions对长连接流式响应的限制:
- 平台网关会因长时间未收到数据返回而触发超时
- 默认的响应缓冲区未主动刷新,导致数据滞留在服务端
setInterval的并发执行可能引发资源竞争,延长响应时间
解决方案
1. 适配Vercel环境的响应头部
移除无效的Connection: keep-alive,添加分块传输头部,明确告知网关处理流式数据:
res.writeHead(200, { "Transfer-Encoding": "chunked", "Cache-Control": "no-cache, no-transform", "Content-Type": "text/event-stream", });
2. 强制刷新响应缓冲区
每次调用res.write后执行res.flush(),确保数据立即发送到客户端,避免网关超时:
if (newMessages.length > 0) { lastTimestamp = newMessages[newMessages.length - 1].timestamp; res.write(`data: ${JSON.stringify(newMessages)} `); await res.flush(); // 强制刷新缓冲区 }
3. 替换setInterval为递归setTimeout
避免setInterval在异步Redis请求未完成时重复触发,减少资源竞争:
const pollMessages = async () => { console.log("GETTING MESSAGES"); try { const newMessages = await REDIS.getChatMessages({ topic, fromTimestamp: lastTimestamp + 1, }); console.log("NEW MESSAGES", newMessages); if (newMessages.length > 0) { lastTimestamp = newMessages[newMessages.length - 1].timestamp; res.write(`data: ${JSON.stringify(newMessages)} `); await res.flush(); } count += 1; if (count <= MAX_RUNS) { setTimeout(pollMessages, REFRESH_INTERVAL); } else { console.log("ENDING"); res.end(); } } catch (err) { console.error("POLL ERROR", err); res.end(); } }; // 启动轮询 pollMessages();
4. 优化连接关闭处理
简化关闭事件监听,避免重复调用res.end():
const cleanup = () => { res.end(); }; res.on("close", cleanup); res.socket?.on("close", cleanup);
修改后的完整代码
// pages/api/chat.ts import { NextApiRequest, NextApiResponse } from "next"; import { REDIS } from "@/serverApi"; const MAX_RUNS = 30; const REFRESH_INTERVAL = 1000; export const config = { maxDuration: 60, }; const handler = async (req: NextApiRequest, res: NextApiResponse) => { console.log("STARTING"); let lastTimestamp = Number(req.query.lastTimestamp) || 0; const topic = (req.query.topic as string) || ""; let count = 0; res.writeHead(200, { "Transfer-Encoding": "chunked", "Cache-Control": "no-cache, no-transform", "Content-Type": "text/event-stream", }); const cleanup = () => { res.end(); }; res.on("close", cleanup); res.socket?.on("close", cleanup); const pollMessages = async () => { console.log("GETTING MESSAGES"); try { const newMessages = await REDIS.getChatMessages({ topic, fromTimestamp: lastTimestamp + 1, }); console.log("NEW MESSAGES", newMessages); if (newMessages.length > 0) { lastTimestamp = newMessages[newMessages.length - 1].timestamp; res.write(`data: ${JSON.stringify(newMessages)} `); await res.flush(); } count += 1; if (count <= MAX_RUNS) { setTimeout(pollMessages, REFRESH_INTERVAL); } else { console.log("ENDING"); cleanup(); } } catch (err) { console.error("POLL ERROR", err); cleanup(); } }; pollMessages(); }; export default handler;
额外注意事项
- 确保Redis连接在Vercel环境下可正常访问,避免因网络问题导致请求阻塞
- 若仍出现超时,可适当缩短
MAX_RUNS或延长REFRESH_INTERVAL,减少函数运行时间 - Vercel的Serverless Functions流式响应建议配合平台的Edge Functions使用,Edge Functions对长连接支持更好(若你的应用适配Edge环境)
内容的提问来源于stack exchange,提问作者user2634633
相关产品推荐
相关产品推荐

