Building an AI chatbot used to require gluing together a half-dozen libraries and writing a lot of boilerplate. The Vercel AI SDK changes that: it gives you a unified API for streaming text, tool calls, and multi-turn conversations that works with OpenAI, Anthropic, Google, and more — all from a Next.js project you probably already have.
This tutorial walks you through a working chatbot from scratch. By the end you will have a streaming chat UI with message history, running locally and deployable to Vercel in one command.
What You Will Build
- A
/api/chatroute that streams responses from an LLM - A React chat UI that renders tokens as they arrive
- Persistent conversation history passed on every request
- A clean project structure you can extend
Prerequisites: Node.js 18+, a free OpenAI API key (or any provider the AI SDK supports), and basic familiarity with Next.js.
Step 1 — Create a Next.js Project
If you are starting from zero, scaffold a new app with the App Router:
npx create-next-app@latest ai-chatbot --typescript --tailwind --app
cd ai-chatbotAccept the defaults. The --app flag enables the App Router, which is required for the streaming approach used in this tutorial.
Step 2 — Install the Vercel AI SDK
npm install ai @ai-sdk/openaiaiis the core SDK — it provides thestreamTextfunction and theuseChathook.@ai-sdk/openaiis the OpenAI provider. Swap it for@ai-sdk/anthropicor@ai-sdk/googleif you prefer a different model.
Step 3 — Add Your API Key
Create a .env.local file at the project root:
OPENAI_API_KEY=sk-...Never commit this file. It is already in the default .gitignore generated by create-next-app.
Step 4 — Create the API Route
The AI SDK is designed for Next.js Route Handlers. Create the file app/api/chat/route.ts:
import { openai } from '@ai-sdk/openai';
import { streamText } from 'ai';
// Allow streaming responses up to 30 seconds
export const maxDuration = 30;
export async function POST(req: Request) {
const { messages } = await req.json();
const result = streamText({
model: openai('gpt-4o-mini'),
system: 'You are a helpful assistant. Be concise and clear.',
messages,
});
return result.toDataStreamResponse();
}A few things to note:
messagesfollows the standard{ role, content }array format. The client sends the full conversation history on every request so the model has context.streamTextreturns a stream.toDataStreamResponse()converts it to aResponsethe browser can consume incrementally.maxDurationprevents Vercel's default 10-second function timeout from cutting off long responses.
Step 5 — Build the Chat UI
Replace the contents of app/page.tsx with a minimal but functional chat interface:
'use client';
import { useChat } from 'ai/react';
export default function ChatPage() {
const { messages, input, handleInputChange, handleSubmit, isLoading } =
useChat();
return (
<main className="flex flex-col h-screen max-w-2xl mx-auto p-4">
<h1 className="text-2xl font-bold mb-4">AI Chatbot</h1>
{/* Message list */}
<div className="flex-1 overflow-y-auto space-y-4 mb-4">
{messages.map((m) => (
<div
key={m.id}
className={`p-3 rounded-lg ${
m.role === 'user'
? 'bg-blue-100 ml-auto max-w-md'
: 'bg-gray-100 mr-auto max-w-md'
}`}
>
<p className="text-sm font-semibold capitalize mb-1">{m.role}</p>
<p className="text-sm whitespace-pre-wrap">{m.content}</p>
</div>
))}
{isLoading && (
<div className="bg-gray-100 p-3 rounded-lg mr-auto max-w-md">
<p className="text-sm text-gray-500">Thinking...</p>
</div>
)}
</div>
{/* Input form */}
<form onSubmit={handleSubmit} className="flex gap-2">
<input
value={input}
onChange={handleInputChange}
placeholder="Ask something..."
disabled={isLoading}
className="flex-1 border rounded-lg px-4 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-blue-400"
/>
<button
type="submit"
disabled={isLoading || !input.trim()}
className="bg-blue-500 text-white px-4 py-2 rounded-lg text-sm disabled:opacity-50"
>
Send
</button>
</form>
</main>
);
}useChat handles all the state: it tracks messages, manages input, fires the POST /api/chat request, and appends each incoming token to the last message as the stream arrives. You get streaming for free without any manual fetch or ReadableStream code.
Step 6 — Run It Locally
npm run devOpen http://localhost:3000. Type a message and watch the response stream in token by token. The full conversation history is sent with each request, so the model remembers what you said earlier in the session.
Step 7 — Deploy to Vercel
npx vercelWhen prompted, add OPENAI_API_KEY as an environment variable in the Vercel dashboard (Settings > Environment Variables). Streaming works on Vercel out of the box — no extra configuration needed.
Practical Tips
Swap models without rewriting logic. Change openai('gpt-4o-mini') to anthropic('claude-3-5-haiku-20241022') after installing @ai-sdk/anthropic. The messages format and streaming behavior are identical across providers.
Add a system prompt per user. Pass a dynamic system string based on session data (e.g., the user's name or subscription tier) to personalise responses without changing the client.
Limit conversation length. Long histories increase latency and cost. Trim messages to the last N turns on the server before passing them to streamText:
const recentMessages = messages.slice(-10);Handle errors gracefully. The useChat hook exposes an error field. Render it in the UI so users know when something went wrong rather than seeing a silent failure.
Rate-limit your route. Without protection, anyone can call your API and burn through your quota. Libraries like @upstash/ratelimit with Vercel KV add per-IP limits in under 20 lines.
Going Further
Once the basic loop works, the AI SDK supports:
- Tool calls — let the model call functions you define (search, calculate, look up data)
- Structured output — use
generateObjectto get typed JSON back instead of free text - Multi-modal input — pass images alongside text with compatible models
- Resumable streams — recover from network drops mid-stream
The official AI SDK documentation is well-maintained and covers all of these patterns with working examples.