#Next.js#React

用 Next.js 16 构建 AI 应用:Server Actions 与流式输出

做一个 AI 应用,前后端要面对三件事:安全地调 LLM、把流式 token 实时推给前端、管理会话状态。本文用 Next.js 16 的 App Router + Server Actions + AI SDK,演示一个结构清晰、可流式、可上生产的 AI 对话页完整实现。

本文要点

  • 主题聚焦:Next.js、React
  • 适合人群:独立开发者、全栈工程师、AI 应用开发者
  • 核心价值:快速理解背景、实践路径、常见误区和工程建议
作者 @admin·发布于 2026年7月19日·更新于 2026年7月23日

做 AI 应用,本质上是在解三道题:怎么安全地调用 LLM(别把 API key 暴露到前端)、怎么把流式 token 实时推给用户、怎么在多轮交互里管理状态。Next.js 16 的 App Router 恰好为这三件事提供了顺手的原语:Server Components / Server Actions 让密钥留在服务端,streaming + AI SDK 让 token 边生成边显示,Cookie/数据库承载会话。

下面用一套完整的最小实现讲清楚。

一、项目结构

text
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 解析。

ts
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)

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)接管状态和流式接收:

tsx
"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)关联:

ts
"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 拉历史,就是连续的多轮对话。

六、上生产要注意的三件事

  1. 限流与防滥用。 AI 调用是按 token 计费的,接口必须加限流(按用户/IP),防刷。upstash/ratelimit 或中间件都行。
  2. 输入校验。 用 zod 校验 input,限制长度、过滤危险内容。Server Action 不是"天然安全"——它是个公开入口。
  3. 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

相关推荐