How to Build a Simple AI-Powered Chatbot with Next.js and Claude

本文为原文前 6,000 字符的节选翻译,完整内容请查看原文。

Title: How to Build a Simple AI-Powered Chatbot with Next.js and Claude

Originally published at jy-labs.com. Updated 2026-10-07. You build an AI chatbot with Next.js and Claude from two files: an App Router route handler that calls streamText from the Vercel AI SDK, and a client component that renders the stream with useChat. This version uses Next.js 16.4, AI SDK 7, and Claude Opus 5.5. Every file below type-checks and passes next build as of October 7, 2026.

本文最初发布于 jy-labs.com,更新于 2026 年 10 月 7 日。你可以通过两个文件构建一个基于 Next.js 和 Claude 的 AI 聊天机器人:一个是调用 Vercel AI SDK 中 streamText 的 App Router 路由处理器,另一个是使用 useChat 渲染流式传输的客户端组件。此版本使用 Next.js 16.4、AI SDK 7 和 Claude Opus 5.5。截至 2026 年 10 月 7 日,以下所有文件均通过了类型检查和 next build。

The problem The 2024 version of this post used the OpenAI SDK and a hand-written fetch loop. Both are out of date. AI SDK 7 renamed system to instructions, dropped Node 20, and replaced result.toUIMessageStreamResponse() with two standalone helpers. Next.js 16 turned on Cache Components, which fails the build on the default useChat call. You want code you copy once and run, with the model, the cost, and the security tradeoffs stated up front.

问题所在:本文 2024 年的版本使用了 OpenAI SDK 和手写的 fetch 循环,两者均已过时。AI SDK 7 将 system 重命名为 instructions,放弃了对 Node 20 的支持,并将 result.toUIMessageStreamResponse() 替换为两个独立的辅助函数。Next.js 16 启用了缓存组件(Cache Components),这会导致默认的 useChat 调用构建失败。你需要的是一段可以即拷即用、且预先说明模型、成本和安全权衡的代码。

The approach Step 1: Scaffold the project and install three packages You need Node.js 22 or later. AI SDK 7 sets “engines”: { “node”: ”>=22” } in its package.json and dropped support for Node 18 and 20. I verified this build on Node 22.23.1. Create the app with Tailwind and the App Router, then add the AI SDK packages: npx create-next-app@latest claude-chat —ts —tailwind —eslint —app cd claude-chat npm install ai @ai-sdk/anthropic @ai-sdk/react

实现方法 第一步:搭建项目并安装三个包。你需要 Node.js 22 或更高版本。AI SDK 7 在其 package.json 中设置了 "engines": { "node": ">=22" },并放弃了对 Node 18 和 20 的支持。我在 Node 22.23.1 上验证了此构建。使用 Tailwind 和 App Router 创建应用,然后添加 AI SDK 包:npx create-next-app@latest claude-chat --ts --tailwind --eslint --app,进入目录 cd claude-chat,运行 npm install ai @ai-sdk/anthropic @ai-sdk/react。

Versions this post was tested against on October 7, 2026: next 16.4.0 react 19.3.0 ai 7.0.131 @ai-sdk/anthropic 4.0.75 @ai-sdk/react 4.0.134 zod 4.6.5 (pulled in as a peer dependency, you do not import it here) All three AI SDK packages are ESM-only in version 7. If you have an older require() based config somewhere, convert it to import first.

本文于 2026 年 10 月 7 日测试所用的版本为:next 16.4.0、react 19.3.0、ai 7.0.131、@ai-sdk/anthropic 4.0.75、@ai-sdk/react 4.0.134、zod 4.6.5(作为对等依赖项引入,此处无需导入)。所有三个 AI SDK 包在 7 版本中均为纯 ESM。如果你在某处有基于 require() 的旧配置,请先将其转换为 import。

Step 2: Put the API key where the browser cannot see it Create an API key in the Claude Console, then add it to .env.local at the project root: ANTHROPIC_API_KEY=sk-ant-… The @ai-sdk/anthropic provider reads ANTHROPIC_API_KEY from the environment by default, so you never pass the key in code. Two rules keep it private: Never prefix it with NEXT_PUBLIC_. Next.js inlines any NEXT_PUBLIC_ variable into the client bundle. Only import @ai-sdk/anthropic from server code. In this tutorial the only import lives in the route handler. The page component imports @ai-sdk/react and ai, neither of which touches the key. Add .env.local to .gitignore if create-next-app did not already do it. On Vercel, set the same variable under Project Settings, Environment Variables, and leave it unchecked for the client.

第二步:将 API 密钥放置在浏览器无法访问的地方。在 Claude 控制台中创建 API 密钥,然后将其添加到项目根目录的 .env.local 文件中:ANTHROPIC_API_KEY=sk-ant-...。@ai-sdk/anthropic 提供程序默认从环境变量中读取 ANTHROPIC_API_KEY,因此你无需在代码中传递密钥。两条规则可确保其私密性:永远不要添加 NEXT_PUBLIC_ 前缀(Next.js 会将任何 NEXT_PUBLIC_ 变量内联到客户端包中);仅在服务器代码中导入 @ai-sdk/anthropic。在本教程中,唯一的导入位于路由处理器中。页面组件导入的是 @ai-sdk/react 和 ai,它们都不会触及该密钥。如果 create-next-app 尚未添加,请将 .env.local 添加到 .gitignore 中。在 Vercel 上,在“项目设置”的“环境变量”下设置相同的变量,并确保不要勾选客户端访问选项。

Step 3: Write the route handler Create app/api/chat/route.ts. This is the only file that talks to Anthropic. import { anthropic } from ‘@ai-sdk/anthropic’; import { convertToModelMessages, createUIMessageStreamResponse, streamText, toUIMessageStream, type UIMessage, } from ‘ai’; // One place to change the model. See the FAQ for the cost of each option. const MODEL = ‘claude-opus-5-5’; // Only the last N messages go to the model. Caps input tokens per request. const MAX_HISTORY = 20; // Allow streaming responses up to 30 seconds on Vercel. export const maxDuration = 30; export async function POST(req: Request) { const { messages }: { messages: UIMessage[] } = await req.json(); const result = streamText({ model: anthropic(MODEL), instructions: ‘You are a concise assistant for a small business website. ’ + ‘Answer in plain language. If you do not know, say so.’, messages: await convertToModelMessages(messages.slice(-MAX_HISTORY)), maxOutputTokens: 1024, // Stops the Anthropic request when the browser aborts the fetch. abortSignal: req.signal, providerOptions: { anthropic: { // Chat does not need deep reasoning. ‘low’ cuts latency and output tokens. effort: ‘low’, // If Claude’s safety classifiers decline a request, Anthropic re-runs it // on a fallback model inside the same call. The provider adds the beta header. fallbacks: ‘default’, }, }, onError: ({ error }) => { console.error(‘[chat] stream error’, error); }, }); return createUIMessageStreamResponse({ stream: toUIMessageStream({ stream: result.stream, // The client sees this string instead of the raw error. onError: () => ‘The assistant is unavailable right now. Try again in a moment.’, }), }); }

第三步:编写路由处理器。创建 app/api/chat/route.ts。这是唯一与 Anthropic 通信的文件。(代码略,见原文)。

What each piece does: anthropic(MODEL) builds the model reference. claude-opus-5-5 is the current Opus model. The FAQ covers swapping to Sonnet or Haiku. instructions is the system prompt. AI SDK 7 renamed it from system. The old name still works with a deprecation warning. Version 7 also rejects role: “system” entries inside messages by default, so if you persist chat history, keep system text out of it. convertToModelMessages strips UI metadata from the UIMessage[] the client sends and returns the ModelMessage[] shape the model expects. It is async in version 6 and later, so await it. messages.slice(-MAX_HISTORY) bounds input tokens. Without it, a long session re-sends the whole transcript on every turn and your cost grows with conversation length. maxOutputTokens: 1024 caps the reply. Raise it if your use case needs long answers. abortSignal: req.signal cancels the Anthropic request when the user clicks Stop. Without it the server keeps generating tokens you pay for and nobody reads. effort: “low” tells Claude to spend fewer thinking tokens. Opus 5.5 defaults to medium. A website chat widget rarely needs more than low, and the difference shows up in both latency and output cost. fallbacks: “default” opts into Anthropic server-side refusal fallbacks. If a safety classifier declines a request, the API re-runs it on a fallback model in the same call. The provider adds the required beta header for you. toUIMessageStream plus createUIMessageStreamResponse replace the result.toUIMessageStreamResponse() method from version 6. The old method still works in 7 with a warning and is scheduled for removal in the next major. The onError on toUIMessageStream controls what text the browser sees. The SDK masks errors by default and sends the literal string “An error occurred.” Return your own string there. The onError on streamText is for server logs only.

各部分功能说明:anthropic(MODEL) 构建模型引用。claude-opus-5-5 是当前的 Opus 模型。FAQ 中涵盖了如何切换到 Sonnet 或 Haiku。instructions 即系统提示词,AI SDK 7 将其从 system 重命名而来,旧名称仍可使用但会触发弃用警告。7 版本默认拒绝消息中的 role: "system" 条目,因此如果持久化聊天记录,请勿包含系统文本。convertToModelMessages 会从客户端发送的 UIMessage[] 中剥离 UI 元数据,并返回模型预期的 ModelMessage[] 格式。它在 6 及更高版本中是异步的,因此需要 await。messages.slice(-MAX_HISTORY) 用于限制输入 Token,否则长会话会在每次交互时重新发送整个记录,导致成本随对话长度增加。maxOutputTokens: 1024 限制了回复长度。abortSignal: req.signal 可在用户点击“停止”时取消 Anthropic 请求。effort: "low" 指示 Claude 减少思考 Token 的消耗。fallbacks: "default" 启用了 Anthropic 服务器端的拒绝回退机制。toUIMessageStream 和 createUIMessageStreamResponse 取代了 6 版本中的 result.toUIMessageStreamResponse() 方法。toUIMessageStream 上的 onError 控制浏览器看到的内容,SDK 默认会屏蔽错误并发送“An error occurred”,你可以在此处返回自定义字符串。streamText 上的 onError 仅用于服务器日志。