The Vercel AI SDK is the fastest way to add streaming AI to a Next.js application. It provides a unified interface for OpenAI, Anthropic, Google, and other providers, handles streaming responses and React state management, and gives you structured output generation with Zod validation. Switching model providers requires changing one import. This guide covers the key APIs you will use in production.
What the SDK Provides
The SDK solves four problems that come up immediately when building AI applications:
Provider abstraction. The API formats for OpenAI, Anthropic, and Google are all different. The Vercel AI SDK wraps all of them behind a common interface. You write your AI logic once and switch providers by changing the model argument.
Streaming to the client. Streaming requires coordinating server-sent events or chunked responses with client-side state updates. The SDK handles this boilerplate with streamText on the server and useChat/useCompletion on the client.
Structured output. Getting a model to return reliably structured JSON is surprisingly tricky. The SDK's generateObject function uses Zod schemas to validate and type model output, retrying automatically if the model returns invalid structure.
Tool calling. Defining tools, parsing tool call responses, and managing the multi-turn tool use loop is complex to build from scratch. The SDK handles this with a tools parameter and manages the conversation state.
Installation and Provider Setup
pnpm add ai @ai-sdk/openai @ai-sdk/anthropic
Provider configuration:
import { openai } from "@ai-sdk/openai";
import { anthropic } from "@ai-sdk/anthropic";
// OpenAI (uses OPENAI_API_KEY env var)
const model = openai("gpt-4o");
// Anthropic (uses ANTHROPIC_API_KEY env var)
const model = anthropic("claude-3-5-sonnet-20241022");
// Switching providers: change one line
streamText: Server-Side Streaming
streamText is the core server-side function. It calls the model and returns a stream you can pipe to the client.
import { streamText } from "ai";
import { openai } from "@ai-sdk/openai";
export async function POST(req: Request) {
const { messages } = await req.json();
const result = streamText({
model: openai("gpt-4o"),
system: "You are a helpful assistant.",
messages,
});
return result.toDataStreamResponse();
}
toDataStreamResponse() returns a Response object with the correct headers for streaming. The Vercel AI SDK client hooks know how to consume this format.
For non-streaming generation, use generateText:
import { generateText } from "ai";
const { text } = await generateText({
model: openai("gpt-4o"),
prompt: "Summarize this document: ...",
});
useChat: Drop-In Chat Interface
useChat is a React hook that manages message history, input state, and the streaming connection to your API route.
"use client";
import { useChat } from "ai/react";
export function Chat() {
const { messages, input, handleInputChange, handleSubmit, isLoading } =
useChat({ api: "/api/chat" });
return (
<div>
<div>
{messages.map((m) => (
<div key={m.id}>
<span>{m.role}: </span>
<span>{m.content}</span>
</div>
))}
</div>
<form onSubmit={handleSubmit}>
<input value={input} onChange={handleInputChange} />
<button type="submit" disabled={isLoading}>Send</button>
</form>
</div>
);
}
useChat handles: posting messages to your API route, appending streaming tokens to the last message as they arrive, managing the loading state, and maintaining the full conversation history for multi-turn conversations. This covers the majority of chat UI requirements with minimal code.
useCompletion: Single-Turn Completion
For non-chat use cases (text generation triggered by a user action, not a conversation), useCompletion is simpler than useChat:
"use client";
import { useCompletion } from "ai/react";
export function SummarizeButton({ document }: { document: string }) {
const { completion, complete, isLoading } = useCompletion({
api: "/api/summarize",
});
return (
<div>
<button onClick={() => complete(document)} disabled={isLoading}>
Summarize
</button>
{completion && <p>{completion}</p>}
</div>
);
}
generateObject: Structured Output with Zod
generateObject is one of the most useful SDK functions for production applications. It guarantees the model returns output matching a Zod schema, retrying and coercing as needed.
import { generateObject } from "ai";
import { openai } from "@ai-sdk/openai";
import { z } from "zod";
const TaskSchema = z.object({
title: z.string(),
priority: z.enum(["high", "medium", "low"]),
estimatedHours: z.number(),
tags: z.array(z.string()),
});
const { object } = await generateObject({
model: openai("gpt-4o"),
schema: TaskSchema,
prompt: "Extract task details from: " + userMessage,
});
// object is typed as z.infer<typeof TaskSchema>
console.log(object.title); // string
console.log(object.priority); // "high" | "medium" | "low"
This is cleaner and more reliable than asking the model to return JSON and parsing it yourself. The SDK handles the system prompt injection that instructs the model to return JSON, parses the response, validates it against the schema, and returns a fully typed object.
Tool Calling
Tools let the model call functions to retrieve information or take actions. The SDK manages the tool call loop:
import { streamText, tool } from "ai";
import { z } from "zod";
const result = streamText({
model: openai("gpt-4o"),
tools: {
getWeather: tool({
description: "Get the current weather for a city",
parameters: z.object({
city: z.string(),
}),
execute: async ({ city }) => {
const weather = await fetchWeather(city);
return weather;
},
}),
},
prompt: "What is the weather in Tokyo?",
maxSteps: 5, // allow up to 5 tool call rounds
});
The SDK handles the multi-turn loop: model calls a tool, SDK executes the tool function, result is sent back to the model, model continues. maxSteps prevents infinite loops.
Next.js App Router Integration
The SDK is designed for Next.js App Router. Server-side AI calls work in Server Components, Route Handlers, and Server Actions.
Route Handler pattern (most common):
// 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-3-5-sonnet-20241022"),
messages,
});
return result.toDataStreamResponse();
}
Server Action pattern:
// app/actions/ai.ts
"use server";
import { generateObject } from "ai";
import { openai } from "@ai-sdk/openai";
export async function extractTask(userInput: string) {
const { object } = await generateObject({
model: openai("gpt-4o-mini"),
schema: TaskSchema,
prompt: userInput,
});
return object;
}
Why Use the SDK vs Calling APIs Directly
The case for direct API calls: you have more control, fewer abstractions, and can handle edge cases exactly as you want.
The case for the Vercel AI SDK: the abstractions are well-designed for common cases, and what you give up in control you gain in time. Streaming to the client correctly, handling multi-provider differences, managing the tool call loop, and integrating with React state are all solved problems in the SDK that take meaningful engineering effort to build correctly from scratch.
Use the SDK when you are building on Next.js and your requirements fit the standard patterns. Build directly against provider APIs when you have specific requirements (custom streaming format, unusual tool call patterns, performance constraints) that the SDK's abstractions do not accommodate.
Keep Reading
- Anthropic API Guide — direct API usage for cases where you need more control
- OpenAI API Guide — OpenAI API patterns the SDK builds on top of
- Cutting LLM API Costs — cost optimization patterns including model selection in the SDK
Pristren builds AI-powered software for teams. Zlyqor is our all-in-one workspace — chat, projects, time tracking, AI meeting summaries, and invoicing — in one tool. Try it free.