Next.js接大模型流式SSE实操踩坑

先把结论甩出来:Next.js App Router 里做大模型流式输出,服务端用 Route Handler 直接返回一个 ReadableStream,把模型 SSE 透传给浏览器;前端别急着上 EventSource(它只支持 GET,传不了 body),用 fetch + response.body.getReader() 自己读流就行。整套打通其实就两个文件的事,难点都在那些没人提的细节里。

事情起因是上周我给自己那个内部小工具加了个对话框。之前一次性等模型把几百字吐完再渲染,白屏五六秒,我自己都嫌卡。改成流式,逐字蹦出来,体感立刻不一样了。

服务端:route handler 把流透传出去

app/api/chat/route.ts 长这样:

javascript 复制代码
export const runtime = 'edge' // node 也行,edge 冷启动快点

export async function POST(req: Request) {
  const { messages } = await req.json()

  const upstream = await fetch('https://你的模型网关/v1/chat/completions', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      Authorization: `Bearer ${process.env.LLM_KEY}`,
    },
    body: JSON.stringify({ model: 'xxx', messages, stream: true }),
  })

  // 直接把上游的 SSE 流当成自己的响应体往下游灌
  return new Response(upstream.body, {
    headers: {
      'Content-Type': 'text/event-stream; charset=utf-8',
      'Cache-Control': 'no-cache, no-transform',
      Connection: 'keep-alive',
    },
  })
}

注意 no-transform 那一行,别省。我一开始没写,本地好好的,一上 Nginx 反代就变成等全部生成完才一次性返回------代理把流给缓冲了,排查了快一个钟头才反应过来是这破玩意儿。

如果上游格式跟你前端想要的对不上,就别裸透传,用 TransformStream 中间拆一道 data: 行再重新拼,逻辑也就十几行,这里不展开了。

前端:fetch 读流,逐字渲染

javascript 复制代码
const res = await fetch('/api/chat', {
  method: 'POST',
  body: JSON.stringify({ messages }),
})

const reader = res.body!.getReader()
const decoder = new TextDecoder()
let buf = ''

while (true) {
  const { done, value } = await reader.read()
  if (done) break
  buf += decoder.decode(value, { stream: true })

  // SSE 一条条 \n\n 切,注意半截 chunk 要留着下一轮拼
  const lines = buf.split('\n\n')
  buf = lines.pop() ?? ''
  for (const line of lines) {
    if (!line.startsWith('data:')) continue
    const data = line.slice(5).trim()
    if (data === '[DONE]') return
    const delta = JSON.parse(data).choices[0].delta.content ?? ''
    setText((t) => t + delta) // 蹦字
  }
}

那个 buf.pop() 留半截的细节是真容易翻车------一个 chunk 不保证正好切在 \n\n 上,UTF-8 中文还可能被切两半,TextDecoder{ stream: true } 帮你兜住后半个问题,前半个得自己用 buffer 接。

一点题外的取舍

代码不长,但调通那天我顺手干了件偷懒的事。模型这块我没自己折腾,挂了个零代码就能配智能体的平台------拖一拖把提示词、知识库配好,发布成一个 API,我前端这边只管 fetch 那个地址。说实话第一版我让那小助手当客服答疑,回答干巴巴像背说明书,回去把知识库文档喂细了点、温度调低,第二版才像样。它也就帮我省了搭后端和调模型的活,前端流式这套该写还得写,逐字渲染的边界问题它一个都不替你扛。

学习曲线倒不算陡,但配 RAG 那块文档分块策略我摸了大半天才找到手感,不是点两下就完美。

模型/API 我走的讯飞 MaaS,现成调,没自己部署算力,省了一台 GPU 机器钱。

你们接流式踩过哪些代理缓冲、中文截断的坑?评论区聊聊,我赌一半人栽在 no-transform 上。

相关推荐
Assby1 小时前
从 Function Calling 到 MCP:理解 Agent 工具调用的底层通信机制
人工智能·后端
小星AI2 小时前
Claude Code 从入门到精通,一步到位
人工智能
后端小肥肠2 小时前
Codex + Obsidian 做人生副本视频:输入主题文案,直通剪映草稿
人工智能·aigc·agent
百度Geek说3 小时前
全链路研发智能体 ——从"体感能用"到"实际可用"的工程实践
人工智能
甲维斯4 小时前
500块的豆包,能帮我搞定这个么?!
人工智能
火山引擎开发者社区4 小时前
当 Agent 自己做 SRE:详解 ArkClaw 自动化可观测体系的工程实践
人工智能
Coffeeee6 小时前
两个例子,帮你快速理解什么是Token
人工智能·程序员·ai编程
饼干哥哥6 小时前
用AI全自动剪辑,日更 100条爆款视频——HyperFrames、Remotion、Git使用入门
人工智能·机器学习·ai编程
用户83244598541326 小时前
深入拆解 AlexNet:跟着一张猫咪照片,看数据如何流动
人工智能