基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
  • 知识卡片碎片时间过考点
  • 场景篇按分类整理的大前端场景考点
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
  • 动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
    • AI 热点每日 AI 资讯与动向
AI 工作台
简历
旧版
AIHT
内容
精选全部 AI 动态热点榜AI 日报收藏
分类
模型行业编程产品技术观点开源
更多
Agent 接入
返回首页开发者导航
AIHT
精选 / 资讯详情
8.0
热点
AI SCORE
技术实践2026-11-06 18:02

用Next.js流式输出缩短聊天等待感

dev.to · AI#Next.js#流式输出#AI应用
Editor brief · 编辑速览

教程说明如何让 Next.js 服务端转发模型生成片段,并由浏览器逐步读取和显示。示例强调在流开始前完成鉴权、输入校验和限额检查;流式输出改善等待体验,不会直接加快模型生成。

文章思维导图
Knowledge map
拖拽缩放
AI deep read

AI 深度解读

AI 回答要生成 8 秒,页面就必须让用户盯着转圈等 8 秒吗?原文给出的办法是流式响应:模型每生成一段,服务端就往浏览器转发一段,让用户先读起来,而不是等整篇答案完成。

这没有让模型算得更快,却能缩短用户看不到任何结果的时间。对 Next.js 开发者,更值得读的是后半部分:中文乱码、回答中断、取消后继续计费,以及上线后流式输出突然失效,都藏在这条看似简单的数据通道里。

流式响应改变的是等待方式

语言模型逐段生成文本,许多 AI API 可以在生成过程中持续返回内容。接入流式响应,需要打通两端:服务端持续发送,客户端持续读取。

链路很直观:模型产生片段,Route Handler 转发片段,浏览器收到后更新界面。任何一环攒够全文再交给下一环,用户看到的仍然是长时间等待。

原文用「完整答案需要 8 秒,首批文字在 1 秒或更短时间出现」说明体验差异,但没有提供模型、网络环境或测试数据。这是示意场景,不能当成接入后的性能承诺。

流式输出优先改善首次看到内容的时间,不保证缩短完整答案的生成时间。

服务端的关键,是把模型输出接到 ReadableStream 上:

// app/api/chat/route.ts
export async function POST(req: Request) {
  const { prompt } = await req.json();

  // 1. 在开始流式输出前完成认证、校验和额度限制。

  const encoder = new TextEncoder();

  const stream = new ReadableStream({
    async start(controller) {
      try {
        for await (const chunk of model.stream({ prompt, signal: req.signal })) {
          controller.enqueue(encoder.encode(chunk.text));
        }
        controller.close();
      } catch (err) {
        controller.error(err);
      }
    },
  });

  return new Response(stream, {
    headers: {
      'Content-Type': 'text/plain; charset=utf-8',
      'Cache-Control': 'no-cache, no-transform',
    },
  });
}

注意 controller.enqueue(encoder.encode(chunk.text)):流传输的是字节,所以要先用 TextEncoder 编码,再推入响应。new Response(stream) 允许响应在流关闭前发送内容,但实际何时到达浏览器,还受部署链路影响。

这里的 model.stream 是占位接口,需要替换成供应商 SDK。原文假设它返回可异步迭代的片段,每段文本位于 chunk.text;具体 SDK 的字段和调用方式仍需适配。

浏览器收到一段字节,不代表收到完整字符

客户端不能继续等完整响应,而要通过 res.body.getReader() 循环读取。下面这段代码同时展示了增量读取和字符解码:

async function ask(
  prompt: string,
  onText: (text: string) => void,
  signal?: AbortSignal
) {
  const res = await fetch('/api/chat', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ prompt }),
    signal,
  });

  if (!res.ok || !res.body) throw new Error('Request failed');

  const reader = res.body.getReader();
  const decoder = new TextDecoder();
  let text = '';

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

  text += decoder.decode(); // 刷出解码器中剩余的内容
  onText(text);
}

最容易漏掉的是 { stream: true } 和最后一次无参数的 decoder.decode()。前者让解码器暂存尚未收全的字符字节,后者完成收尾。

网络分块不按文字边界切。一个中文字符或 Emoji 的字节,可能分散在两个块里。如果每块各自解码,不完整的字节就可能变成 �。只测英文,很容易错过这个问题。

收到文本以后,也不必每个小块都立即触发 React 更新。原文建议最多每个动画帧更新一次状态;对于 Markdown,可以生成中先显示纯文本,结束后再格式化。这样能减少频繁解析,也能避免未闭合的 ** 或代码块让已有内容反复变样。

代价是用户要等生成结束才能看到格式。短回答直接逐块更新通常足够,长回答再按实际卡顿情况优化。

上线前要查的是整条链路

流式响应把一次请求拆成了两个错误处理阶段。

开始发送之前,输入错误、未登录、触发限流等情况,应返回正常的错误响应,例如 Response.json({ error }, { status: 4xx }),客户端通过 res.ok 判断。认证和成本限制也要放在这里,避免先调用模型,再发现请求不该被允许。

开始发送之后,示例响应的 HTTP 状态已经是 200,无法在模型中途失败时改成 500。此时流会报错,客户端的 reader.read() 可能拒绝执行,需要捕获异常、保留已有回答,并提示「回答已中断,请重试」。

这里原文有个实现缺口:配套 React 示例的异常分支调用 setAnswer('Something went wrong. Try again.'),会覆盖已有内容,与后文「保留已收到的回答」不一致。落地时应把回答文本和错误提示分开存储。

取消同样需要贯穿两端。浏览器用 AbortController 把信号传给 fetch;服务端把 req.signal 继续传给模型 SDK。只停止界面更新,不能证明上游生成已经结束。

作者也承认,连接断开后的行为可能因托管平台而异。因此需要发起一次长回答,点击 Stop,再检查供应商用量,确认取消是否真正传递上去了。

另外两项问题往往只在部署后出现:

  • 反向代理、压缩层或 CDN 可能缓冲响应,导致本地逐字显示,上线后整篇一起到达。Cache-Control: no-cache, no-transform 是检查项之一,仍需核对路由上的缓冲、压缩和平台流式支持配置。
  • 长回答可能超过 Serverless 函数时限。原文给出的配置是 export const maxDuration = 60;,单位为秒,实际允许值取决于托管套餐。同时还要限制模型输出长度,但原文没有给出对应 SDK 参数。

我的判断

这篇最有用的地方,是把「能显示流式文字」和「能可靠交付回答」之间的距离讲清楚了。解码、错误、取消、缓冲、超时,直接对应用户会遇到的故障,适合作为代码评审时的检查依据。

不过,「约 30 行就能完成」只适合描述最小通路。代码里的认证、校验和限流仍是注释,模型接口也是占位写法。它展示了机制,没有交付完整的生产方案。

作者自述用 Next.js 做 SaaS,但没有展示线上测量结果、取消后的账单对比或渲染性能数据。因此,「流式输出是成本最低的提速方式」更接近经验判断。

和等待完整 JSON 响应相比,纯文本流能更早展示内容,却需要承担中途失败和取消的处理成本。对于逐步阅读的聊天回答,这个交换通常合理;如果业务必须拿到完整结果才能继续操作,原文也没有证明逐段展示一定更合适。

能拿来做什么

  • 给现有 Next.js 聊天页接流式输出时,先打通模型、Route Handler 和浏览器,再用中文、Emoji 及长回答检查解码和读取逻辑。
  • 把界面状态拆成回答文本、生成状态和错误提示,让主动停止、中途失败、正常结束各有明确表现,避免异常覆盖已经生成的内容。
  • 在真实部署环境验证两件事:首段是否持续到达,以及点击 Stop 后上游是否停止生成。前者查代理链路,后者查取消传播和供应商用量。
  • 如果长回答或 Markdown 渲染出现卡顿,再采用每帧合并更新、结束后格式化,并结合托管时限设置 maxDuration 和模型输出上限。
Original source

本文由 AI 通读原文后重新编写,观点部分为站内判断,细节以原文为准,来源 dev.to · AI,原文版权归原作者所有。

阅读原文
上一篇
智能体评测也需要持续集成
下一篇
用RAG打通测试文档与结果查询

资讯速览

dev.to · AI · 2026-11-06 18:02

AI 评分 8.0 / 10

相关标签

#Next.js#流式输出#AI应用

继续阅读

  • 接入代码仓库前,先理清智能体权限
  • 智能体评测也需要持续集成
  • 用RAG打通测试文档与结果查询
  • 让智能体状态可查、进度可恢复
  • 测试全绿仍有漏洞:断言别照搬实现