Next.js + Vercel AI SDK 实战:30 分钟搭出流式 Chat 页面

《AI 前端实战》第 2/8 篇

本篇目标:从 0 跑通可流式输出的 Chat 最小闭环

上篇:能力地图 · 下篇预告:Streaming 工程化(重连 / 合并 / 限流)

第 1 篇把能力分成了 L1→L4。本篇只做一件事:把你从 L1(等完整 JSON) 拉到 L2(流式对话)

目标产物:

  • Next.js App Router 项目
  • /api/chat 服务端流式接口
  • 前端 useChat 对话页
  • Markdown 边生成边渲染(基础防闪烁)

30 分钟能跑通;跑不通也能按文末 checklist 自查。

你将学到

  • 为什么要用 AI SDK,而不是手写 fetch + res.json()
  • App Router 下 Route Handler 怎么返回流
  • useChat 如何管理消息列表与停止生成
  • 流式 Markdown 的最小可用写法

一、先说结论:别从手写 Stream 解析开始

你可以自己解析 ReadableStream,也能做成流式。但第一版更建议用 Vercel AI SDK,原因很务实:

  1. 统一多模型 Provider(OpenAI / Anthropic / 兼容网关)
  2. 前端有现成 useChat,消息状态不用从零设计
  3. 协议稳定,后续接 Tool Calling 成本更低

本篇默认:

  • Next.js 15+(App Router)
  • AI SDK(ai + @ai-sdk/openai 或兼容包)
  • TypeScript

没有 OpenAI Key 也没关系:很多国内网关提供 OpenAI 兼容接口,改 baseURL 即可。

二、30 分钟路线图

分钟 动作 验收
0--5 建项目、装依赖、配 Key pnpm dev 能起
5--15 /api/chat curl / 浏览器能看到流
15--25 写 Chat 页面 + useChat 页面能边打字边出字
25--30 Markdown 渲染 + 停止按钮 代码块不太闪,可停止

三、初始化项目

bash 复制代码
npx create-next-app@latest ai-chat-demo --typescript --app --tailwind --eslint
cd ai-chat-demo
pnpm add ai @ai-sdk/openai react-markdown

环境变量 .env.local

bash 复制代码
OPENAI_API_KEY=sk-xxx
# 若用兼容网关,再加:
# OPENAI_BASE_URL=https://your-gateway/v1

四、服务端:/api/chat Route

创建 app/api/chat/route.ts

ts 复制代码
import { openai } from "@ai-sdk/openai";
import { streamText } from "ai";

export const maxDuration = 60;

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

  const result = streamText({
    model: openai("gpt-4o-mini"), // 可换成你的模型名
    system: "你是简洁、靠谱的前端助手,优先给可执行建议。",
    messages,
  });

  return result.toDataStreamResponse();
}

关键点:

  • streamText,不是一次性 generateText
  • 返回 toDataStreamResponse(),前端 useChat 才能直接消费
  • maxDuration 给长回答留时间(部署平台不同,限制不同)

本地快速验证(可选):

bash 复制代码
curl -N http://localhost:3000/api/chat \
  -H 'Content-Type: application/json' \
  -d '{"messages":[{"role":"user","content":"用一句话解释 RSC"}]}'

能持续吐出数据,而不是等很久才一次性返回,就对了。

五、前端:useChat 对话页

创建 app/page.tsx

tsx 复制代码
"use client";

import { useChat } from "ai/react";
import ReactMarkdown from "react-markdown";

export default function Page() {
  const { messages, input, handleInputChange, handleSubmit, isLoading, stop } =
    useChat();

  return (
    <main className="mx-auto max-w-2xl p-6 space-y-4">
      <h1 className="text-2xl font-semibold">AI Chat(流式)</h1>

      <div className="space-y-3 min-h-[320px] border rounded-xl p-4">
        {messages.map((m) => (
          <div key={m.id} className="text-sm">
            <div className="opacity-60 mb-1">
              {m.role === "user" ? "你" : "助手"}
            </div>
            {m.role === "assistant" ? (
              <ReactMarkdown>{m.content}</ReactMarkdown>
            ) : (
              <p>{m.content}</p>
            )}
          </div>
        ))}
        {isLoading && <p className="text-xs opacity-60">生成中...</p>}
      </div>

      <form onSubmit={handleSubmit} className="flex gap-2">
        <input
          className="flex-1 border rounded-lg px-3 py-2"
          value={input}
          onChange={handleInputChange}
          placeholder="问点前端的问题..."
        />
        {isLoading ? (
          <button type="button" onClick={stop} className="px-4 py-2 border rounded-lg">
            停止
          </button>
        ) : (
          <button type="submit" className="px-4 py-2 border rounded-lg">
            发送
          </button>
        )}
      </form>
    </main>
  );
}

你得到的能力:

  • 消息列表自动维护
  • 流式增量更新 assistant 内容
  • 一键 stop 中断生成

这已经是可用的 L2 雏形。

六、Markdown 流式:先做到「能看」,再追求完美

流式场景下,Markdown 最容易出现:

  • 代码围栏没闭合,整段样式乱跳
  • 列表项半截导致闪烁
  • 表格未完成时布局抖动

第 2 篇先用最小方案:

  1. 助手消息用 react-markdown 渲染
  2. 生成中显示「生成中...」状态
  3. 不在流式过程做重型语法高亮(可留到稳定后再高亮)

进阶优化(下篇会展开):

  • 未闭合 code fence 时降级为纯文本
  • 对 thinking / tool / final 分通道渲染
  • 用稳定 key 和更细粒度组件减少重绘

七、本地跑通 Checklist

按顺序勾:

  • .env.local 里 Key 已生效(改完要重启 pnpm dev
  • /api/chat 返回的是流,不是整包 JSON
  • 页面发送后,助手文字是「一个个出来」
  • 点击「停止」后,不再继续增长内容
  • 刷新后你理解:默认 useChat 不持久化历史(这是正常的)

常见报错对照:

现象 排查
401 / invalid api key Key 或 baseURL 错
一直转圈无字 Route 没 toDataStreamResponse,或前端没 useChat
CORS 同域 App Router 一般无此问题;别跨域硬调
内容一次性蹦出 你可能在服务端先 await 完再返回

八、这版故意没做的事(留给后面)

本篇为了 30 分钟闭环,刻意不做:

  1. 断线重连
  2. 会话持久化
  3. Tool Calling
  4. 多模型路由与成本控制
  5. 生产级监控

这些会在第 3~8 篇逐步补齐。先有一个「能流式聊」的底座,后面才有地方挂工程能力。

九、最小目录结构(对照)

text 复制代码
ai-chat-demo/
  app/
    api/chat/route.ts
    page.tsx
    layout.tsx
  .env.local
  package.json

如果你用的是 AI SDK 新版本,hook 导入路径可能是 ai/react 或文档当前推荐路径,以你安装版本的官方文档为准;协议思想不变:服务端 streamText,前端 useChat

小结

本篇把你推进到 L2:

  1. 服务端用 streamText 产出流
  2. 前端用 useChat 消费流并管理消息
  3. Markdown 增量渲染 + 停止生成

这就是 AI 前端最小可用闭环。接下来问题会变成:

网络抖了怎么办?消息乱了怎么办?工具调用中途挂了怎么办?


下篇预告 :《AI 前端实战》第 3/8 篇

Streaming UI 工程化:SSE、断线重连、消息合并与限流降级。

先把今天的 Demo 跑通。跑通后再看下篇,体感会完全不一样。

相关推荐
曦尧1 小时前
Anthropic 开源 11 个知识工作插件:用 Markdown 文件把 Claude 变成岗位专家
ai·自动化
阿萨德528号1 小时前
Kimi K3:全球首个开源 3T 级大模型,技术解读与手记
人工智能·ai·语言模型·开源
云空2 小时前
《Three.js 完整版3D魔方:带贴纸+中心固定+自动求解复原》
前端·javascript·3d·three.js
酸梅果茶2 小时前
【7】lightning_lm项目-LIO 前端 -IVox 局部地图
前端·slam
@atweiwei2 小时前
Langchainrust:中LLM-as-a-Judge,用 Rust 实现一套 LLM 评估系统
开发语言·后端·ai·rust·llm·rag
@灯神2 小时前
SpringAI系列|第1篇:SpringAI概述与快速上手
java·人工智能·spring·ai·ai编程
gis开发之家2 小时前
《Vue3 从入门到大神33篇》Vue3 源码详解(三):响应式核心思想——从 Object.defineProperty 到 Proxy
前端·javascript·vue.js·vue3·vue3源码
Hyyy2 小时前
流式渲染的基础——SSE
前端·后端·面试
Csvn2 小时前
📐 Layout Thrashing——一个 `offsetHeight` 让你的页面卡成 PPT
前端