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 hooksuseChat and useCompletion for chat UIs
  • Tool use — Define tools with Zod schemas
  • Structured outputgenerateObject for 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:

  1. Receives the tool call from Claude
  2. Executes the execute function
  3. Sends the result back to Claude
  4. Claude generates a response using the tool result
  5. 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:

  1. Go to your project settings
  2. Navigate to Environment Variables
  3. Add ANTHROPIC_API_KEY with 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

FeatureVercel AI SDKAnthropic SDK
Streaming to browserBuilt-in (toDataStreamResponse)Manual SSE handling
React hooksuseChat, useCompletionBuild your own
Tool useZod schemas, automatic executionManual tool loop
Structured outputgenerateObject with validationManual JSON parsing
Provider switchingChange one lineRewrite API calls
Type safetyFull TypeScript + ZodManual types
OverheadSmall (~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

ScenarioModelCost per Request
Chat message (short)Sonnet 4.6~$0.008
Chat message (long conversation)Sonnet 4.6~$0.03
Structured extractionSonnet 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

  1. Use maxTokens to limit response length
  2. Trim conversation history after 10-15 messages
  3. Use Haiku for simple tasks (classification, short answers)
  4. 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

ConceptCode
Installnpm install ai @ai-sdk/anthropic zod
Generate textgenerateText({ model: anthropic("claude-sonnet-4-6"), prompt })
Stream textstreamText({ model, messages }).toDataStreamResponse()
Chat hookuseChat() in client component
Tool usetool({ parameters: z.object({...}), execute })
Structured outputgenerateObject({ model, schema, prompt })
Stream UIstreamUI({ 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.