This guide builds a complete AI chatbot from scratch using Next.js App Router and Claude. By the end, you’ll have a streaming chatbot with conversation history, ready to deploy to Vercel.
What We’re Building
- Next.js 15 App Router project
- Streaming AI responses using Vercel AI SDK
- Claude 3.5 Sonnet as the model
- Persistent conversation history in the UI
- Clean chat UI with Tailwind CSS
Setup
npx create-next-app@latest ai-chatbot --typescript --tailwind --app
cd ai-chatbot
npm install ai @ai-sdk/anthropic
Add your API key to .env.local:
ANTHROPIC_API_KEY=sk-ant-...
API Route
Create app/api/chat/route.ts:
import { anthropic } from '@ai-sdk/anthropic';
import { streamText } from 'ai';
export const maxDuration = 30;
export async function POST(req: Request) {
const { messages } = await req.json();
const result = streamText({
model: anthropic('claude-3-5-sonnet-20241022'),
system: 'You are a helpful assistant.',
messages,
});
return result.toDataStreamResponse();
}
Chat UI Component
Create app/components/Chat.tsx:
'use client';
import { useChat } from 'ai/react';
export default function Chat() {
const { messages, input, handleInputChange, handleSubmit, isLoading } = useChat();
return (
<div className="flex flex-col h-screen max-w-2xl mx-auto p-4">
{/* Messages */}
<div className="flex-1 overflow-y-auto space-y-4 mb-4">
{messages.length === 0 && (
<div className="text-center text-gray-400 mt-8">
Start a conversation with Claude
</div>
)}
{messages.map((message) => (
<div
key={message.id}
className={`flex ${message.role === 'user' ? 'justify-end' : 'justify-start'}`}
>
<div
className={`max-w-[80%] rounded-2xl px-4 py-2 ${
message.role === 'user'
? 'bg-blue-600 text-white'
: 'bg-gray-100 text-gray-900'
}`}
>
<p className="whitespace-pre-wrap">{message.content}</p>
</div>
</div>
))}
{isLoading && (
<div className="flex justify-start">
<div className="bg-gray-100 rounded-2xl px-4 py-2">
<div className="flex space-x-1">
<div className="w-2 h-2 bg-gray-400 rounded-full animate-bounce" />
<div className="w-2 h-2 bg-gray-400 rounded-full animate-bounce" style={{ animationDelay: '0.2s' }} />
<div className="w-2 h-2 bg-gray-400 rounded-full animate-bounce" style={{ animationDelay: '0.4s' }} />
</div>
</div>
</div>
)}
</div>
{/* Input */}
<form onSubmit={handleSubmit} className="flex gap-2">
<input
value={input}
onChange={handleInputChange}
placeholder="Ask anything..."
className="flex-1 rounded-full border border-gray-300 px-4 py-2 focus:outline-none focus:border-blue-500"
disabled={isLoading}
/>
<button
type="submit"
disabled={isLoading || !input.trim()}
className="bg-blue-600 text-white rounded-full px-6 py-2 disabled:opacity-50 hover:bg-blue-700"
>
Send
</button>
</form>
</div>
);
}
Page
Update app/page.tsx:
import Chat from './components/Chat';
export default function Home() {
return (
<main>
<Chat />
</main>
);
}
Adding a System Prompt Selector
Extend the chatbot with configurable system prompts:
// app/api/chat/route.ts
const SYSTEM_PROMPTS: Record<string, string> = {
default: 'You are a helpful assistant.',
coder: 'You are an expert developer. Provide working code with explanations.',
writer: 'You are a writing coach. Help improve writing with specific feedback.',
analyst: 'You are a data analyst. Provide structured analysis and insights.',
};
export async function POST(req: Request) {
const { messages, persona = 'default' } = await req.json();
const result = streamText({
model: anthropic('claude-3-5-sonnet-20241022'),
system: SYSTEM_PROMPTS[persona] ?? SYSTEM_PROMPTS.default,
messages,
});
return result.toDataStreamResponse();
}
Adding Rate Limiting
npm install @upstash/ratelimit @upstash/redis
// app/api/chat/route.ts
import { Ratelimit } from '@upstash/ratelimit';
import { Redis } from '@upstash/redis';
const ratelimit = new Ratelimit({
redis: Redis.fromEnv(),
limiter: Ratelimit.slidingWindow(10, '1 m'), // 10 requests per minute
});
export async function POST(req: Request) {
const ip = req.headers.get('x-forwarded-for') ?? '127.0.0.1';
const { success } = await ratelimit.limit(ip);
if (!success) {
return new Response('Too many requests', { status: 429 });
}
const { messages } = await req.json();
// ... rest of handler
}
Deployment to Vercel
vercel
Add environment variable in Vercel dashboard:
ANTHROPIC_API_KEY— your Anthropic API key
The API route automatically becomes a serverless function with the correct runtime settings.
Extensions to Build Next
- Persistent history — Save conversations to database (Supabase/Neon)
- User authentication — Add Clerk or NextAuth
- File uploads — Allow users to upload PDFs for analysis
- Tool calls — Give Claude access to web search or your data
- Custom styling — Replace Tailwind utilities with your design system
This foundation handles the core chatbot pattern. Each extension adds to it without changing the base architecture.