The Vercel AI SDK is the most popular way to use Claude in web applications. It handles streaming, tool use, and UI state — all with a few lines of code. In this article, you will build a complete chat application with Claude, Next.js, and the Vercel AI SDK.
This is Article 27 in the Claude AI — From Zero to Power User series. This is the final article in the series. You should know the Messages API and Tool Use before this article.
Why Vercel AI SDK?
The Vercel AI SDK (ai package) provides:
- Streaming — Stream Claude responses to the browser in real time
- React hooks —
useChatanduseCompletionfor chat UIs - Tool use — Define tools with Zod schemas
- Structured output —
generateObjectfor typed responses - Provider agnostic — Switch between Claude, GPT, Gemini with one line
Install
npx create-next-app@latest my-claude-app
cd my-claude-app
npm install ai @ai-sdk/anthropic zod
Set your API key in .env.local:
ANTHROPIC_API_KEY=sk-ant-...
Basic Text Generation — Server Side
Use generateText for server-side Claude calls:
// app/api/generate/route.ts
import { generateText } from "ai";
import { anthropic } from "@ai-sdk/anthropic";
export async function POST(req: Request) {
const { prompt } = await req.json();
const { text } = await generateText({
model: anthropic("claude-sonnet-4-6"),
prompt,
});
return Response.json({ text });
}
This calls Claude on the server and returns the full response. Good for one-shot generation, but the user waits for the entire response.
Streaming Text — Real-Time Responses
Use streamText to stream Claude’s response to the browser character by character:
// app/api/chat/route.ts
import { streamText } from "ai";
import { anthropic } from "@ai-sdk/anthropic";
export async function POST(req: Request) {
const { messages } = await req.json();
const result = streamText({
model: anthropic("claude-sonnet-4-6"),
system: "You are a helpful assistant. Be concise.",
messages,
});
return result.toDataStreamResponse();
}
The toDataStreamResponse() method converts the stream into a format that the useChat hook can consume.
Chat UI with useChat Hook
The useChat hook handles everything: sending messages, receiving streamed responses, and managing conversation state.
// app/page.tsx
"use client";
import { useChat } from "ai/react";
export default function ChatPage() {
const { messages, input, handleInputChange, handleSubmit, isLoading } =
useChat();
return (
<div className="mx-auto max-w-2xl p-4">
<h1 className="mb-4 text-2xl font-bold">Claude Chat</h1>
<div className="mb-4 space-y-4">
{messages.map((message) => (
<div
key={message.id}
className={`rounded-lg p-3 ${
message.role === "user"
? "bg-blue-100 text-blue-900"
: "bg-gray-100 text-gray-900"
}`}
>
<p className="text-sm font-semibold">
{message.role === "user" ? "You" : "Claude"}
</p>
<p className="whitespace-pre-wrap">{message.content}</p>
</div>
))}
</div>
<form onSubmit={handleSubmit} className="flex gap-2">
<input
value={input}
onChange={handleInputChange}
placeholder="Type a message..."
className="flex-1 rounded-lg border p-2"
disabled={isLoading}
/>
<button
type="submit"
disabled={isLoading}
className="rounded-lg bg-blue-500 px-4 py-2 text-white disabled:opacity-50"
>
{isLoading ? "..." : "Send"}
</button>
</form>
</div>
);
}
That is a complete chat application. The useChat hook:
- Manages the message array
- Sends messages to
/api/chat - Streams the response
- Updates the UI in real time
- Tracks loading state
Tool Use with Zod Schemas
Define tools that Claude can call. The Vercel AI SDK uses Zod for type-safe tool definitions:
// app/api/chat/route.ts
import { streamText, tool } from "ai";
import { anthropic } from "@ai-sdk/anthropic";
import { z } from "zod";
export async function POST(req: Request) {
const { messages } = await req.json();
const result = streamText({
model: anthropic("claude-sonnet-4-6"),
system: "You are a helpful assistant with access to tools.",
messages,
tools: {
getWeather: tool({
description: "Get the current weather for a location",
parameters: z.object({
location: z.string().describe("City name"),
}),
execute: async ({ location }) => {
// In production, call a real weather API
const weather = {
location,
temperature: 22,
unit: "celsius",
condition: "Partly cloudy",
};
return weather;
},
}),
calculate: tool({
description: "Calculate a math expression",
parameters: z.object({
expression: z.string().describe("Math expression to evaluate"),
}),
execute: async ({ expression }) => {
try {
// Simple eval for demo — use a proper math library in production
const result = Function(`return ${expression}`)();
return { expression, result };
} catch {
return { expression, error: "Invalid expression" };
}
},
}),
},
maxSteps: 5, // Allow up to 5 tool use rounds
});
return result.toDataStreamResponse();
}
When Claude decides to use a tool, the SDK:
- Receives the tool call from Claude
- Executes the
executefunction - Sends the result back to Claude
- Claude generates a response using the tool result
- The response streams to the browser
Structured Output with generateObject
Use generateObject to get typed, validated responses:
// app/api/extract/route.ts
import { generateObject } from "ai";
import { anthropic } from "@ai-sdk/anthropic";
import { z } from "zod";
const ProductSchema = z.object({
name: z.string(),
price: z.number(),
currency: z.string(),
features: z.array(z.string()),
rating: z.number().min(0).max(5),
});
export async function POST(req: Request) {
const { description } = await req.json();
const { object } = await generateObject({
model: anthropic("claude-sonnet-4-6"),
schema: ProductSchema,
prompt: `Extract product information from: ${description}`,
});
// `object` is fully typed as { name: string, price: number, ... }
return Response.json(object);
}
The response is validated against the Zod schema. If Claude’s response does not match, the SDK retries automatically.
Streaming React Components with streamUI
streamUI lets you stream React components from the server. Claude decides which component to render:
// app/api/ui/route.ts
import { streamUI } from "ai/rsc";
import { anthropic } from "@ai-sdk/anthropic";
import { z } from "zod";
export async function POST(req: Request) {
const { messages } = await req.json();
const result = await streamUI({
model: anthropic("claude-sonnet-4-6"),
messages,
text: ({ content }) => <div className="prose">{content}</div>,
tools: {
showWeather: {
description: "Show weather for a location",
parameters: z.object({ location: z.string() }),
generate: async function* ({ location }) {
yield <div>Loading weather for {location}...</div>;
// Fetch weather data
const weather = await fetchWeather(location);
return (
<div className="rounded-lg border p-4">
<h3 className="font-bold">{location}</h3>
<p>{weather.temperature}°C — {weather.condition}</p>
</div>
);
},
},
},
});
return result.value;
}
With streamUI, Claude can render loading states, data cards, charts — any React component.
Server Components Integration
Use Claude in React Server Components for server-side rendering:
// app/summary/page.tsx
import { generateText } from "ai";
import { anthropic } from "@ai-sdk/anthropic";
export default async function SummaryPage() {
const { text } = await generateText({
model: anthropic("claude-sonnet-4-6"),
prompt: "Give me 3 productivity tips for developers.",
});
return (
<div className="p-4">
<h1 className="text-2xl font-bold">Daily Tips</h1>
<p className="mt-4 whitespace-pre-wrap">{text}</p>
</div>
);
}
The Claude call happens at build time (or request time if not cached). The user sees the result instantly with no client-side loading.
Deploying to Vercel
Environment Variables
Add your API key in the Vercel dashboard:
- Go to your project settings
- Navigate to Environment Variables
- Add
ANTHROPIC_API_KEYwith your key
Deploy
# Install Vercel CLI
npm install -g vercel
# Deploy
vercel
# Deploy to production
vercel --prod
Edge Functions
By default, Next.js API routes run as serverless functions. For lower latency, use edge runtime:
// app/api/chat/route.ts
export const runtime = "edge";
export async function POST(req: Request) {
// ... same code as before
}
Edge functions run closer to the user and have faster cold starts. However, they have limited Node.js API access.
Vercel AI SDK vs Direct Anthropic SDK
| Feature | Vercel AI SDK | Anthropic SDK |
|---|---|---|
| Streaming to browser | Built-in (toDataStreamResponse) | Manual SSE handling |
| React hooks | useChat, useCompletion | Build your own |
| Tool use | Zod schemas, automatic execution | Manual tool loop |
| Structured output | generateObject with validation | Manual JSON parsing |
| Provider switching | Change one line | Rewrite API calls |
| Type safety | Full TypeScript + Zod | Manual types |
| Overhead | Small (~50KB client) | None |
Use Vercel AI SDK when: Building a web app with React/Next.js.
Use Anthropic SDK directly when: Building a backend service, CLI tool, or need full control over the API.
Cost Considerations
| Scenario | Model | Cost per Request |
|---|---|---|
| Chat message (short) | Sonnet 4.6 | ~$0.008 |
| Chat message (long conversation) | Sonnet 4.6 | ~$0.03 |
| Structured extraction | Sonnet 4.6 | ~$0.005 |
| Tool use (2 tool calls) | Sonnet 4.6 | ~$0.02 |
For a chat app with 1,000 messages per day, expect about $8-30/day depending on conversation length.
Tips for Cost Control
- Use
maxTokensto limit response length - Trim conversation history after 10-15 messages
- Use Haiku for simple tasks (classification, short answers)
- Cache system prompts with prompt caching
Complete Project Structure
my-claude-app/
├── app/
│ ├── api/
│ │ ├── chat/
│ │ │ └── route.ts # Streaming chat endpoint
│ │ └── extract/
│ │ └── route.ts # Structured output endpoint
│ ├── page.tsx # Chat UI
│ └── layout.tsx # App layout
├── .env.local # ANTHROPIC_API_KEY
├── package.json
└── tsconfig.json
Summary
| Concept | Code |
|---|---|
| Install | npm install ai @ai-sdk/anthropic zod |
| Generate text | generateText({ model: anthropic("claude-sonnet-4-6"), prompt }) |
| Stream text | streamText({ model, messages }).toDataStreamResponse() |
| Chat hook | useChat() in client component |
| Tool use | tool({ parameters: z.object({...}), execute }) |
| Structured output | generateObject({ model, schema, prompt }) |
| Stream UI | streamUI({ model, tools, text }) |
Series Complete
This is the final article in the Claude AI — From Zero to Power User series. You have learned everything from your first API call to building production applications.
Here is what you covered:
- Part 1: API setup, models, prompt engineering, CLAUDE.md
- Part 2: Messages API, tools, vision, structured output, caching
- Part 3: Streaming, web search, MCP, computer use, agents
- Part 4: CLI chatbot, code review bot, document Q&A, blog writer, CI/CD, cost optimization, and this web app
Go build something. The Claude Cheat Sheet is your quick reference whenever you need a code snippet.