Claude API Integration with Next.js: Full App Guide
Integrating the Claude API into a Next.js app means calling Claude from a server-side route handler, never from the browser, and streaming the response back to your React components. This guide walks through the exact setup: project structure, environment variables, the API route, the client-side fetch with streaming, and the production details people usually miss (rate limits, error handling, and cost control).
If you're building a chat feature, a content generator, or an AI-assisted form in Next.js, the pattern is the same regardless of which framework version you're on (App Router or Pages Router). The core rule: your Claude API key stays on the server. Everything below assumes that.
Project Setup
Install the Anthropic SDK (or skip it and use fetch directly — both work fine in Next.js):
npm install @anthropic-ai/sdk
Add your key to .env.local:
ANTHROPIC_API_KEY=sk-ant-...
Never prefix this variable with NEXT_PUBLIC_. That prefix exposes the variable to the browser bundle, which means your key ends up in client-side JavaScript — a common and expensive mistake.
Building the API Route (App Router)
Create a route handler at app/api/chat/route.ts:
import Anthropic from "@anthropic-ai/sdk";
const anthropic = new Anthropic({
apiKey: process.env.ANTHROPIC_API_KEY,
});
export async function POST(req: Request) {
const { messages } = await req.json();
const response = await anthropic.messages.create({
model: "claude-3-5-sonnet-20241022",
max_tokens: 1024,
messages,
});
return Response.json(response);
}
This is a working, non-streaming endpoint. Your Next.js app calls /api/chat, and that route handler talks to Claude on the server.
Adding Streaming
For chat UIs, streaming matters — users see tokens appear immediately instead of waiting for the full response. Update the route to stream:
export async function POST(req: Request) {
const { messages } = await req.json();
const stream = await anthropic.messages.stream({
model: "claude-3-5-sonnet-20241022",
max_tokens: 1024,
messages,
});
const encoder = new TextEncoder();
const readable = new ReadableStream({
async start(controller) {
for await (const event of stream) {
if (event.type === "content_block_delta") {
controller.enqueue(encoder.encode(event.delta.text || ""));
}
}
controller.close();
},
});
return new Response(readable, {
headers: { "Content-Type": "text/plain; charset=utf-8" },
});
}
On the client, consume the stream with the Fetch API's reader:
async function sendMessage(messages: { role: string; content: string }[]) {
const res = await fetch("/api/chat", {
method: "POST",
body: JSON.stringify({ messages }),
});
const reader = res.body!.getReader();
const decoder = new TextDecoder();
let text = "";
while (true) {
const { done, value } = await reader.read();
if (done) break;
text += decoder.decode(value, { stream: true });
setOutput(text); // update React state
}
}
This gives you a working streaming chat interface without any third-party UI library. If you're using React Server Components with the App Router, keep the streaming logic in a client component ("use client") since it depends on browser fetch and state.
Handling Tool Use and Structured Output
If your Next.js app needs Claude to call functions — looking up a database record, hitting an internal API, formatting structured JSON — pass tools in the request body the same way you would in any Claude integration. The route handler pattern doesn't change; you just add a tools array to the messages.create call and handle tool_use content blocks in the response before sending a follow-up message with the tool result.
Where SubToAPI Fits
Calling the Claude API directly works well for a single project, but if you're building several Next.js apps, running an internal tool alongside a customer-facing product, or need per-project usage visibility without juggling multiple Anthropic accounts, SubToAPI turns your existing Claude access into a standard HTTPS API with its own application keys (sub_live_...).
The integration in your route handler barely changes — you swap the base URL and key:
const response = await fetch("https://api.subtoapi.app/v1/messages", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.SUBTOAPI_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
model: "claude-3-5-sonnet-20241022",
max_tokens: 1024,
messages,
}),
});
You get separate keys per app or per environment, usage metadata per key, and team seats if more than one developer is shipping against the same Claude account. Streaming and tool use work the same way as the direct API — see the streaming docs and tools docs for the exact request shapes. The quickstart walks through generating your first key, and pricing covers the Solo, Team, and Scale plans, all with a free trial at signup.
Production Checklist
Before shipping a Claude-powered feature in Next.js:
- Rate limiting: add basic per-user rate limiting on your route handler to avoid one user exhausting your quota.
- Timeouts: set a reasonable
max_tokensand handle slow responses gracefully in the UI. - Error boundaries: wrap the fetch call in try/catch and surface a fallback message — Claude API calls can fail due to network issues or invalid input, not just quota problems.
- Logging: log request/response metadata (not full user content, for privacy) so you can debug issues without re-running expensive calls.
- Cost tracking: know how many tokens each feature consumes, especially if you're running multiple AI features across one app.
Do I need a backend to use Claude with Next.js, or can I call it from the client?
You need a server-side route handler. Calling Claude directly from client-side JavaScript exposes your API key in the browser, which is a security risk and against Anthropic's usage terms for direct key exposure.
Does streaming work with Next.js Server Actions instead of route handlers?
Server Actions can call Claude, but streaming a token-by-token response back to the client is more natural with a route handler returning a ReadableStream, since Server Actions are designed around single request/response cycles.
Can I use the same Claude integration code across multiple Next.js projects?
Yes — since the integration is just an HTTP call, the route handler code is portable across projects. If you're managing multiple apps under one Claude account, a service like SubToAPI lets you issue separate API keys per project for cleaner usage tracking.