Originally published at claudeguide.io/vercel-ai-sdk-claude
Vercel AI SDK + Claude: Streaming, Tool Use & Edge Deploy (2026)
Install @ai-sdk/anthropic, instantiate the provider with anthropic("claude-sonnet-4-6"), and stream responses with streamText() — Claude works on Vercel AI SDK 5.x out of the box with ~150ms cold start on Edge runtime. The SDK works on both the Edge runtime (SSE-friendly, 25s max execution) and Node (long-running tools, native deps). Tool use, vision, and prompt caching all flow through the same streamText API once you know where to attach providerOptions. Three gotchas catch every team: an outdated anthropic-version header, the system-message role mismatch, and a stream-finalize race that swallows partial output. Below is the working setup, with the exact provider config and a tool-call example you can paste into a Next.js route handler.
Install and minimal example
The SDK is provider-agnostic; you pick the model package separately.
bun add ai @ai-sdk/anthropic
# or: npm i ai @ai-sdk/anthropic
Set ANTHROPIC_API_KEY in .env.local. The provider reads it automatically — no client init needed.
A streaming Next.js route handler:
// app/api/chat/route.ts
import { anthropic } from "@ai-sdk/anthropic";
import { streamText, convertToCoreMessages } from "ai";
export const runtime = "edge"; // or "nodejs"
export const maxDuration = 30;
export async function POST(req: Request) {
const { messages } = await req.json();
const result = await streamText({
model: anthropic("claude-sonnet-4-6"),
system: "You are a concise technical assistant.",
messages: convertToCoreMessages(messages),
maxTokens: 1024,
});
return result.toDataStreamResponse();
}
Frontend with useChat():
"use client";
import { useChat } from "ai/react";
export default function Chat() {
const { messages, input, handleInputChange, handleSubmit } = useChat();
return (
<form onSubmit={handleSubmit}
If your hit rate stays under 80%, you are almost certainly paying for cache writes you never read. Log `usage.cacheReadInputTokens` vs `usage.cacheCreationInputTokens` on every response and alert when the ratio inverts.
**[Claude API Cost Optimization Masterclass ($59)](https://shoutfirst.gumroad.com/l/msjkda?utm_source=claudeguide&utm_medium=article&utm_campaign=vercel-sdk-mid)** — Includes the exact caching + streaming patterns for Vercel AI SDK deployments, plus a live cost trace from a 50K-call production agent.
## Edge runtime caveats
The Edge runtime is the right default for streaming Claude responses. Cold start is ~150ms in Vercel's measurements, SSE flushing works, and `Response` streaming is native. But the 25-second hard execution limit will kill long tool chains. Switch to `runtime = "nodejs"` when:
- A tool calls a slow external API (DB migration, scraper, render farm).
- You need filesystem access for large attachments.
- You depend on a native module (`sharp`, `canvas`, native crypto).
- You need durable connections (websockets, long polling) — Vercel functions are not the right primitive; use a worker.
`maxDuration = 300` extends Node functions on Pro plans. Hobby plans cap at 60s. If you need 5+ minute jobs, push to a queue and stream the result back via Server-Sent Events from a separate poller.
## Vision: passing image messages
Image input goes in the same `messages` array as multipart content:
ts
const result = await streamText({
model: anthropic("claude-sonnet-4-6"),
messages: [
{
role: "user",
content: [
{ type: "text", text: "What is in this screenshot?" },
{
type: "image",
image: new URL("https://example.com/screenshot.png"),
// or: image: Buffer.from(...) for base64
},
],
},
],
});
The SDK normalizes URL fetching, base64 encoding, and MIME detection. Anthropic accepts JPEG, PNG, GIF, WebP up to ~5MB per image; the SDK does not enforce the size cap, so do it yourself before paying for a rejected request.
## Three specific gotchas
**1. The `anthropic-version` header default may be outdated.** The pinned version inside `@ai-sdk/anthropic` lags behind the API. If you need a recent feature (extended thinking, files API, the latest tool-use mode), override it:
ts
const model = anthropic("claude-sonnet-4-6", {
headers: { "anthropic-version": "2026-04-01" },
});
Or globally via `providerOptions.anthropic.headers` on the call. Symptom: a feature works in the raw SDK but silently no-ops through Vercel AI SDK.
**2. The SDK normalizes `assistant` and `user` roles but NOT `system`.** Anthropic's API takes `system` as a top-level parameter, not a message. If you put a `{ role: "system" }` entry in `messages`, behavior depends on SDK version: some swallow it, some convert it, some throw. Always pass system text via the top-level `system:` field on `streamText`.
**3. Stream finalize race — check `finishReason` on every chunk.** A stream that ends cleanly has `finishReason: "stop"` or `"tool-calls"`. A stream that ends with `"length"` was truncated by `maxTokens`; `"content-filter"` means a safety block. If you only render `text` and never inspect `finishReason`, your UI silently shows half-answers when limits hit. Wire it through `useChat`'s `onFinish` callback or read `result.finishReason` on the server.
## Production deploy
There is almost no config:
1. `ANTHROPIC_API_KEY` in Vercel project env vars (Production + Preview).
2. `runtime` and `maxDuration` exported from the route handler.
3. Vercel Firewall to rate-limit unauthenticated `/api/chat` calls (Claude requests cost real money — never expose an unprotected endpoint).
4. Cost guardrails — the [Claude API cost monitoring guide](/claude-api-cost-monitoring-guide) shows how to attach per-user spend caps via Anthropic's usage API.
That is it. No region pinning, no warmup, no provider-specific build flags.
## Vercel AI SDK vs raw `@anthropic-ai/sdk`
Skip the Vercel SDK when you need:
- **Batch API** (50% discount on async jobs
## Frequently Asked Questions
### Does it support prompt caching?
Yes, via `providerOptions.anthropic.cacheControl: { type: "ephemeral" }` on the message you want cached. The SDK does not auto-detect cacheable prefixes — you mark them. Verify hits by logging `usage.cacheReadInputTokens` from the `onFinish` callback.
### Edge or Node runtime?
Default to Edge for chat-style apps: faster cold start, native SSE, lower cost on Vercel. Switch to Node when a tool runs longer than 25 seconds, you need a native module, or you call the filesystem. The runtime is per-route — mix freely in the same app.
### Can I switch between Claude and OpenAI dynamically?
Yes. Both providers implement the same `LanguageModelV1` interface, so `streamText({ model: useClaude ? anthropic(...) : openai(...) })` works. Tool definitions are portable; vision message format is identical. The one wart: provider-specific options (cache control, reasoning effort) are siloed under `providerOptions.anthropic` vs `providerOptions.openai`.
### Does useChat work with tool calls?
Yes, with `maxSteps` set on the server and tool-call rendering wired in your message component. The hook exposes `message.toolInvocations` — iterate it to render tool inputs/outputs. Without `maxSteps > 1` the loop stops after the first tool result and Claude never gets to summarize.
### Why is my stream cut off?
Three causes, in order of likelihood: (1) `maxTokens` hit — check `finishReason === "length"`. (2) Edge runtime 25-second timeout — switch to Node or shorten the work. (3) Client disconnected and the SDK aborted — `useChat` handles this, but custom fetchers may need an `AbortController` wired through. Always log `finishReason` server-side; "silent truncation" is almost always one of these three.
---
*Last verified May 2026 against `@ai-sdk/anthropic` v1.x and `ai` v5.x. Model IDs and runtime caps refresh quarterly — check the [Vercel AI SDK changelog](https://sdk.vercel.ai/docs) before pinning to a version.*
Top comments (0)