Vercel AI SDK Basics
8 examples to get you started with the Vercel AI SDK in Next.js - 5 basic and 3 intermediate.
Search across all documentation pages
8 examples to get you started with the Vercel AI SDK in Next.js - 5 basic and 3 intermediate.
You will install the packages, stream a chat with useChat, render message parts, add a tool, and set duration/runtime knobs for real deploys.
pnpm create next-app@latest my-ai-app
cd my-ai-app
pnpm add ai @ai-sdk/react zod
# optional direct provider package:
# pnpm add @ai-sdk/openai# .env.local (names vary by provider - verify at build)
AI_GATEWAY_API_KEY=...
# or OPENAI_API_KEY=... / OPENROUTER_API_KEY=...Pin ai to the major you target (this guide assumes AI SDK 6 APIs; helpers may rename - verify at build).
Create app/api/chat/route.ts that streams model output as a UI message stream.
import {
streamText,
convertToModelMessages,
type UIMessage,
} from "ai";
export const maxDuration = 30;
export async function POST(req: Request) {
const { messages }: { messages: UIMessage[] } = await req.json();
const result = streamText({
model: "openai/gpt-4o-mini", // gateway string or provider model - verify at build
messages: await convertToModelMessages(messages),
instructions: "You are a concise product assistant.",
});
return result.toUIMessageStreamResponse();
}UIMessage[] is the client history shape; convert before calling the model.streamText returns a result you expose as a UI message stream response.createUIMessageStreamResponse + toUIMessageStream, swap the return line - verify at build.Related: The Vercel AI SDK Mental Model: Streaming UI Meets Agent Loops
useChat in a Client Page"use client";
import { useChat } from "@ai-sdk/react";
import { DefaultChatTransport } from "ai";
import { useState } from "react";
export default function ChatPage() {
const [input, setInput] = useState("");
const { messages, sendMessage, status } = useChat({
transport: new DefaultChatTransport({ api: "/api/chat" }),
});
return (
<div>
{messages.map((message) => (
<div key={message.id}>
<strong>{message.role}: </strong>
{message.parts.map((part, i) =>
part.type === "text" ? <span key={i}>{part.text}</span> : null,
)}
</div>
))}
<form
onSubmit={(e) => {
e.preventDefault();
if (!input.trim()) return;
sendMessage({ text: input });
setInput("");
}}
>
<input
value={input}
onChange={(e) => setInput(e.target.value)}
disabled={status !== "ready"}
placeholder="Say something..."
/>
<button type="submit" disabled={status !== "ready"}>
Send
</button>
</form>
</div>
);
}message.parts over a single content string.sendMessage is the AI SDK 5+/6 chat send path; older tutorials used handleSubmit + controlled input from the hook.DefaultChatTransport defaults often target /api/chat even when omitted - be explicit in multi-API apps.const { messages, sendMessage, status, stop, error, regenerate } = useChat({
transport: new DefaultChatTransport({ api: "/api/chat" }),
});
// In JSX:
{(status === "submitted" || status === "streaming") && (
<button type="button" onClick={() => stop()}>
Stop
</button>
)}
{error && (
<button type="button" onClick={() => regenerate()}>
Retry
</button>
)}submitted means the request left but chunks may not have started.streaming means parts are still arriving.import { streamText, tool, convertToModelMessages, isStepCount } from "ai";
import { z } from "zod";
const result = streamText({
model: "openai/gpt-4o-mini",
messages: await convertToModelMessages(messages),
stopWhen: isStepCount(5),
tools: {
getWeather: tool({
description: "Get a weather summary for a city",
inputSchema: z.object({
city: z.string().describe("City name"),
}),
execute: async ({ city }) => ({
city,
summary: "sunny",
tempC: 22,
}),
}),
},
});inputSchema is what the model sees and what the SDK validates.execute runs server-side when present.stopWhen: isStepCount(n) bounds multi-step tool loops (helper name verify at build).{message.parts.map((part, i) => {
if (part.type === "text") return <span key={i}>{part.text}</span>;
if (part.type === "tool-getWeather") {
if (part.state === "input-available") return <div key={i}>Loading weather…</div>;
if (part.state === "output-available") {
return (
<pre key={i}>{JSON.stringify(part.output, null, 2)}</pre>
);
}
}
return null;
})}tool-${toolName}.input-available and output-available drive loading vs result UI.import { openai } from "@ai-sdk/openai";
import { streamText, convertToModelMessages } from "ai";
const result = streamText({
model: openai("gpt-4o-mini"),
instructions:
"Answer in short bullets. If you lack data, say you do not know.",
messages: await convertToModelMessages(messages),
});export const runtime = "edge";
export const maxDuration = 30;
// same streamText handler body as example 1const { messages, sendMessage } = useChat({
transport: new DefaultChatTransport({
api: "/api/chat",
headers: {
// Prefer cookie sessions; tokens only if your auth design requires them
"X-Request-Source": "web-chat",
},
}),
});api at a proxy route that forwards the stream.Related: Integrating Pydantic AI or LangGraph Behind an AI SDK Frontend
Stack versions: Pins from the category manifest (verify at build): OpenRouter (~315+ models, July 2026 pricing/fees); LangGraph 1.0+; CrewAI 1.14+; Microsoft Agent Framework 1.0; Vercel AI SDK 6; Pydantic AI (latest); LlamaIndex (latest); OpenAI Agents SDK (latest + MCP); MCP (Linux Foundation governance); A2A (HTTP+SSE+JSON-RPC 2.0); Solana
@solana/web3.js+@solana/spl-token.
Reviewed by Chris St. John·Last updated Jul 16, 2026