Claude API Integration with a Next.js App
Integrating Claude into a Next.js app comes down to one rule: never call the API from client-side code. Claude API keys (and app-level keys if you're proxying through a service) must stay on the server, which in Next.js means Route Handlers (app/api/.../route.ts) or Server Actions. The client sends a request to your own backend, your backend calls Claude, and the response streams or returns back to the UI.
This guide walks through the actual wiring: where the key lives, how to structure the route, how to stream tokens back to the browser, and which Next.js runtime to pick so you don't hit request timeouts on longer completions.
Where the Claude call belongs
Next.js gives you two server-side places to make the request:
- Route Handlers (
app/api/chat/route.ts) — a normal HTTP endpoint your frontend fetches. Best for streaming UIs and when you want a stable API contract. - Server Actions — functions called directly from a form or client component without you writing a
fetch. Good for simpler, non-streaming use cases like a single generate-and-return button.
Both run on the server, so your ANTHROPIC_API_KEY (or SUBTOAPI_KEY) never reaches the browser bundle. If you see a Claude key in your client JavaScript, something is wrong — grep your NEXT_PUBLIC_* env vars immediately.
Basic Route Handler setup
// app/api/chat/route.ts
export async function POST(req: Request) {
const { messages } = await req.json();
const res = 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-sonnet-4-5",
max_tokens: 1024,
messages,
}),
});
const data = await res.json();
return Response.json(data);
}
On the client, this is a plain fetch to /api/chat. The Next.js app never talks to Claude directly — it talks to itself, and the server does the real call. This same pattern works whether you're calling Anthropic's endpoint directly or a proxy like SubToAPI's /v1/messages (see /docs/messages) — only the base URL and key differ.
Streaming into the UI
Chat interfaces need tokens to appear as they're generated, not after the full response finishes. In a Route Handler, you can pipe the upstream stream straight through:
export async function POST(req: Request) {
const { messages } = await req.json();
const upstream = 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-sonnet-4-5",
max_tokens: 1024,
stream: true,
messages,
}),
});
return new Response(upstream.body, {
headers: { "Content-Type": "text/event-stream" },
});
}
The client reads this with EventSource or a ReadableStream reader and appends tokens to the UI as they arrive. If you want the full client-side parsing logic, that's covered in detail in the streaming guide — the point here is just that Next.js Route Handlers can pass a stream through untouched, which is exactly what a chat UI needs.
Server Actions for simpler cases
If you don't need streaming — a summarizer button, a "generate description" field, a one-shot classification — Server Actions cut out the manual fetch on the client entirely:
// app/actions.ts
"use server";
export async function generateSummary(text: string) {
const res = 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-sonnet-4-5",
max_tokens: 300,
messages: [{ role: "user", content: `Summarize: ${text}` }],
}),
});
const data = await res.json();
return data.content[0].text;
}
Called from a component with await generateSummary(input). No separate API route, no manual JSON parsing on the client — just a function call that happens to run on the server.
Environment variables and runtime choice
Add your key to .env.local (never commit it) and to your hosting provider's environment settings:
SUBTOAPI_KEY=sub_live_xxxxxxxxxxxx
Two things trip people up on deployment:
- Edge vs Node runtime. Some SDKs and streaming setups behave differently under Vercel's Edge runtime. If you're using
fetch-based calls like the examples above, both runtimes generally work — but if you addexport const runtime = "edge"to a Route Handler, test your streaming path specifically, since buffering behavior can differ. - Function timeouts. Default serverless function timeouts (often 10–60s depending on plan) can cut off long completions. If you're generating long-form content without streaming, either increase the timeout in your platform config or switch to a streaming response, which keeps the connection alive incrementally instead of waiting for one large payload.
Managing keys across environments
A common mistake is using the same key in local dev, staging, and production, which makes it impossible to tell where unexpected usage is coming from. With SubToAPI, you generate a separate sub_live_... key per app or environment from the dashboard, each with its own usage tracking — so a runaway dev script doesn't silently eat into your production quota. See /docs/quickstart for the initial setup and /pricing if you're deciding between Solo, Team, and Scale for a project with multiple contributors.
Tool use from a Next.js backend
If your Next.js app needs Claude to call functions — looking up a database record, hitting an internal API, running a calculation — the tool definitions and the tool-result loop both live in your Route Handler, not the client. The client just sends messages and renders whatever comes back, including any intermediate "Claude is looking that up" state you choose to show. Full request/response shapes for this are in /docs/tools.
Summary
For a Next.js app, Claude integration is mostly a question of Next.js architecture, not Claude specifics: keep the key server-side in a Route Handler or Server Action, stream the response through untouched when you need real-time UI updates, and use separate keys per environment so usage stays traceable. Everything else — prompts, models, tool schemas — is the same as any other backend integration.
FAQ
Can I call the Claude API directly from a Next.js client component? No. Client components run in the browser, and any API key included there is visible to anyone who opens dev tools. Always route the call through a Route Handler or Server Action.
Do I need the Edge runtime for streaming to work in Next.js? Not necessarily. Standard Node.js Route Handlers can stream a ReadableStream back to the client fine. Edge is useful for lower cold-start latency, but test your specific streaming setup before relying on it in production.
How do I avoid mixing up dev and production usage on the same API key? Generate separate keys per environment. If you're using SubToAPI, create one sub_live_... key per app or environment from the dashboard (see /docs/quickstart) so usage and billing stay isolated per environment.