用 Next.js 16 构建 AI 应用:Server Actions 与流式输出
做一个 AI 应用,前后端要面对三件事:安全地调 LLM、把流式 token 实时推给前端、管理会话状态。本文用 Next.js 16 的 App Router + Server Actions + AI SDK,演示一个结构清晰、可流式、可上生产的 AI 对话页完整实现。
本文要点
- 主题聚焦:Next.js、React
- 适合人群:独立开发者、全栈工程师、AI 应用开发者
- 核心价值:快速理解背景、实践路径、常见误区和工程建议
做 AI 应用,本质上是在解三道题:怎么安全地调用 LLM(别把 API key 暴露到前端)、怎么把流式 token 实时推给用户、怎么在多轮交互里管理状态。Next.js 16 的 App Router 恰好为这三件事提供了顺手的原语:Server Components / Server Actions 让密钥留在服务端,streaming + AI SDK 让 token 边生成边显示,Cookie/数据库承载会话。
下面用一套完整的最小实现讲清楚。
一、项目结构
app/
chat/page.tsx // 客户端:消息列表 + 输入框
actions.ts // Server Action:调用 LLM 并流式返回
lib/
ai.ts // 模型客户端封装
密钥(ANTHROPIC_API_KEY / OPENAI_API_KEY)只放在服务端环境变量,前端永远拿不到——这是 Server Action 相比"前端直接 fetch 一个 /api 路由"的天然优势:它就是一个服务端函数,不存在被 bundle 到客户端的代码里这回事。
二、模型封装(lib/ai.ts)
用 Vercel 的 ai SDK,它把不同厂商的流式协议统一成一套接口,省去手写 SSE 解析。
import { createAnthropic } from "@ai-sdk/anthropic";
import { streamText } from "ai";
const anthropic = createAnthropic({ apiKey: process.env.ANTHROPIC_API_KEY });
export function chat(system: string, messages: { role: "user" | "assistant"; content: string }[]) {
return streamText({
model: anthropic("claude-sonnet-5"),
system,
messages,
});
}
想换模型只改这一处:
createOpenAI、createGoogleGenerativeAI、createMistral同构。
三、Server Action:流式调用(app/actions.ts)
"use server";
import { chat } from "@/lib/ai";
export async function askAI(input: string) {
const result = chat(
"你是 CodeNodeX AI 社区的助手,用中文简明准确地回答。",
[{ role: "user", content: input }],
);
// streamUI /流式文本:把结果以流的形式返回给客户端组件
return result.toUIMessageStream();
}
Server Action 标了 "use server",前端调用它就像调用一个异步函数,但实际跑在服务端——你既不用写 /api/route,也不用担心 key 泄露。
四、客户端:边收边显示(app/chat/page.tsx)
用 useChat(ai/react)接管状态和流式接收:
"use client";
import { useChat } from "ai/react";
export default function ChatPage() {
const { messages, input, handleInputChange, handleSubmit, isLoading } = useChat({
api: "/api/chat", // 也可直接用 action,这里走 route 更直观
});
return (
<main className="mx-auto max-w-2xl p-6">
<div className="space-y-4">
{messages.map((m) => (
<div key={m.id} className={m.role === "user" ? "text-right" : ""}>
<span className="whitespace-pre-wrap">{m.content}</span>
</div>
))}
{isLoading && <span className="animate-pulse">思考中…</span>}
</div>
<form onSubmit={handleSubmit} className="mt-6 flex gap-2">
<input value={input} onChange={handleInputChange}
className="flex-1 rounded border px-3 py-2" placeholder="问点什么…" />
<button className="rounded bg-black px-4 py-2 text-white">发送</button>
</form>
</main>
);
}
token 一边生成、一边追加到 messages 末尾,用户看到的就是"打字机"效果。这背后是 AI SDK 帮你处理好的 SSE / 流式解析,你只管用 messages。
五、把对话持久化(可选但推荐)
useChat 的状态只在内存里,刷新就没了。要持久化,在 Server Action 里把每轮存进数据库,并按 sessionId(放 cookie)关联:
"use server";
import { cookies } from "next/headers";
import { db } from "@/lib/db";
export async function askAI(input: string) {
const sessionId = cookies().get("sid")?.value ?? crypto.randomUUID();
cookies().set("sid", sessionId);
await db.message.create({ data: { sessionId, role: "user", content: input } });
// ...调用模型、流式返回;返回前/后再存 assistant 消息
}
下次进来按 sessionId 拉历史,就是连续的多轮对话。
六、上生产要注意的三件事
- 限流与防滥用。 AI 调用是按 token 计费的,接口必须加限流(按用户/IP),防刷。upstash/ratelimit 或中间件都行。
- 输入校验。 用 zod 校验
input,限制长度、过滤危险内容。Server Action 不是"天然安全"——它是个公开入口。 - Edge vs Node runtime。 流式响应在 Edge Runtime 上延迟更低、冷启动更小,但不是所有 SDK 都兼容 Edge;上量后值得测一下两种 runtime 的表现。
小结
Next.js 16 做 AI 应用的体验已经相当顺:
- 密钥安全:Server Action/Component 跑在服务端,key 不进前端 bundle。
- 流式简单:AI SDK +
useChat,几十行出一个打字机对话。 - 状态自然:Cookie/数据库承接会话,App Router 的数据流模型够用。
把这三块搭好,一个能上生产的 AI 对话页,一个下午就能交付——剩下的工夫,花在 prompt 调优、限流和产品体验上,比手搓 SSE 管线值钱得多。
Related