Building a chat UI that can next.js stream claude sonnet 4.5 tokens in real time requires wiring the App Router to a streaming LLM provider and parsing the response on the client. This guide walks through a minimal, production-shaped implementation using the Vercel AI SDK and an OpenAI-compatible endpoint, so you own the transport and can swap providers without rewriting components.
Step 1: Scaffold the Next.js App Router project
Start with a clean App Router app. If you already have one, skip to Step 2.
pnpm create next-app@latest my-chat --ts --app --eslint --tailwind --src-dir
cd my-chat
Use the src/ directory so imports stay rooted at @/. The App Router gives you Route Handlers (app/api/.../route.ts) that can return a ReadableStream—exactly what the AI SDK needs for streaming.
Step 2: Install the Vercel AI SDK and OpenAI provider
The Vercel AI SDK abstracts message normalization and stream parsing. Its OpenAI provider package talks to any OpenAI-compatible chat completions endpoint, which lets you target Claude Sonnet 4.5 through a gateway.
pnpm add ai @ai-sdk/openai zod
@ai-sdk/openai exports createOpenAI, which accepts a baseURL. That is the seam where you point at Anthropic directly or at a routing gateway.
Step 3: Configure environment variables
Never hardcode keys. Create .env.local:
# .env.local
LLM_API_KEY=sk-your-key
# Point at your provider. For an OpenRouter-class gateway:
LLM_BASE_URL=https://api.n4n.ai/v1
If you route through n4n.ai, set LLM_BASE_URL to its OpenAI-compatible endpoint; it addresses 240+ models and forwards provider cache-control hints, so the same code works without vendor lock. The model string claude-sonnet-4.5 is passed straight through.
Step 4: Write the streaming API route
Create src/app/api/chat/route.ts. This handler receives messages from the client, calls the model, and returns a streaming response.
// src/app/api/chat/route.ts
import { streamText } from 'ai';
import { createOpenAI } from '@ai-sdk/openai';
export const runtime = 'edge';
const gateway = createOpenAI({
baseURL: process.env.LLM_BASE_URL,
apiKey: process.env.LLM_API_KEY,
});
export async function POST(req: Request) {
const { messages } = await req.json();
const result = streamText({
model: gateway('claude-sonnet-4.5'),
messages,
});
return result.toDataStreamResponse();
}
toDataStreamResponse() emits the AI SDK’s typed stream protocol. The edge runtime keeps cold starts low and supports streaming out of the box on Vercel.
If you need per-token metering or fallback when a provider is degraded, a gateway handles that server-side. Gateways like n4n.ai provide automatic fallback when a provider is rate-limited or degraded, which you get by simply setting the base URL—no client changes.
Step 5: Build the client component
Use useChat from @ai-sdk/react to manage message state and read the stream. Create src/app/page.tsx as a client component:
'use client';
import { useChat } from '@ai-sdk/react';
export default function Chat() {
const { messages, input, handleInputChange, handleSubmit } = useChat({
api: '/api/chat',
});
return (
<div className="mx-auto max-w-2xl p-4">
<div className="space-y-4">
{messages.map((m) => (
<div key={m.id} className="whitespace-pre-wrap">
<strong>{m.role === 'user' ? 'You: ' : 'Claude: '}</strong>
{m.content}
</div>
))}
</div>
<form onSubmit={handleSubmit} className="mt-4 flex gap-2">
<input
value={input}
onChange={handleInputChange}
placeholder="Say something..."
className="flex-1 rounded border p-2"
/>
<button type="submit" className="rounded bg-black px-4 py-2 text-white">
Send
</button>
</form>
</div>
);
}
This component re-renders as tokens arrive. The useChat hook appends assistant text incrementally, so the next.js stream claude sonnet 4.5 output shows up word-by-word without manual fetch plumbing.
Handling multi-turn context
useChat keeps the full message array and sends it on each request. Your route forwards it to the model. For long conversations, add a maxTokens or trim history server-side to avoid context overflow.
const result = streamText({
model: gateway('claude-sonnet-4.5'),
messages: messages.slice(-20), // keep last 20 turns
});
Step 6: Error handling and abort control
Streams fail. Wrap the client submit in an error boundary and expose error from useChat:
const { messages, input, handleInputChange, handleSubmit, error } = useChat();
{error && <div className="text-red-500">Stream failed: {error.message}</div>}
On the server, catch provider errors before they break the stream:
try {
const result = streamText({ model: gateway('claude-sonnet-4.5'), messages });
return result.toDataStreamResponse();
} catch (e) {
return new Response('Model unavailable', { status: 503 });
}
A gateway with automatic fallback reduces the need for this, but you should still return a clean status so the client can retry.
Step 7: Verify the stream end to end
Run the dev server and exercise the path:
pnpm dev
Open http://localhost:3000. Type a prompt and submit. Verification checklist:
- The assistant response appears incrementally, not all at once.
- Open DevTools → Network →
/api/chat→ Response. The content-type should betext/event-streamand payload should bedata: {...}chunks. - Check the server logs (or gateway dashboard) for token usage. If using n4n.ai, per-token usage metering shows up in the request ledger.
- Kill the provider key (set bogus
LLM_API_KEY) to confirm your 503 path or gateway fallback triggers.
If the stream renders as a single block, you likely forgot 'use client' or used fetch instead of useChat. If you get CORS errors, ensure the route is same-origin (it is, by default in App Router).
Step 8: Production hardening
For real traffic, add:
- Rate limiting on
/api/chatvia Upstash or Vercel KV. - Timeout:
streamTextacceptsmaxTokensandtemperature; setmaxRetriesto 2. - Cache hints: pass
headersthrough the gateway if you want provider prompt caching. n4n.ai forwards cache-control hints, so set them once in the route.
const gateway = createOpenAI({
baseURL: process.env.LLM_BASE_URL,
apiKey: process.env.LLM_API_KEY,
headers: { 'x-cache-control': 'prompt-cache' },
});
That is the entire loop. You now have a Next.js App Router app that can next.js stream claude sonnet 4.5 responses with less than 50 lines of route and component code, and you can repoint the base URL to any OpenAI-compatible provider without touching the UI.