Claude API Integration with Next.js: A Full Setup Guide
Integrating the Claude API into a Next.js app means setting up a server-side route that talks to Anthropic (or a gateway like SubToAPI), then connecting that route to your frontend components with proper streaming and error handling. The two things developers usually get stuck on are where to call the API from (never the browser) and how to stream tokens back to the UI without losing React's rendering model.
This guide walks through a complete, production-ready setup: API routes, environment variables, streaming with the App Router, and a simple chat component. It applies whether you're calling Anthropic directly or using a key from a service like SubToAPI, which wraps Claude access behind a standard HTTPS API.
Why server-side only
Claude API keys must never be exposed to the browser. Next.js makes this easy with Route Handlers (App Router) or API Routes (Pages Router) — both run exclusively on the server, so your ANTHROPIC_API_KEY or SUBTOAPI_KEY stays out of client bundles.
.env.local
SUBTOAPI_KEY=sub_live_xxxxxxxxxxxx
Never prefix this variable with NEXT_PUBLIC_ — that prefix tells Next.js to inline it into client JavaScript, which defeats the purpose entirely.
Setting up the route handler
In the App Router (Next.js 13+), create a route under app/api/chat/route.ts:
export const runtime = "edge"; // optional, for faster cold starts
export async function POST(req) {
const { messages } = await req.json();
const response = await fetch("https://api.subtoapi.app/v1/messages", {
method: "POST",
headers: {
"Content-Type": "application/json",
"Authorization": `Bearer ${process.env.SUBTOAPI_KEY}`,
},
body: JSON.stringify({
model: "claude-sonnet-4",
max_tokens: 1024,
messages,
}),
});
if (!response.ok) {
const error = await response.text();
return new Response(error, { status: response.status });
}
const data = await response.json();
return Response.json(data);
}
This is a non-streaming version — good for short responses, summaries, or structured outputs where you want the full JSON object before rendering anything. See /docs/messages for the full request/response shape.
Calling it from a client component
"use client";
import { useState } from "react";
export default function Chat() {
const [input, setInput] = useState("");
const [reply, setReply] = useState("");
const [loading, setLoading] = useState(false);
async function send() {
setLoading(true);
const res = await fetch("/api/chat", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
messages: [{ role: "user", content: input }],
}),
});
const data = await res.json();
setReply(data.content?.[0]?.text ?? "");
setLoading(false);
}
return (
<div>
<textarea value={input} onChange={(e) => setInput(e.target.value)} />
<button onClick={send} disabled={loading}>
{loading ? "Thinking..." : "Send"}
</button>
<p>{reply}</p>
</div>
);
}
This pattern works well for forms, summarizers, or any feature where the user waits for a single complete answer.
Streaming responses for a chat-like UX
For anything conversational, streaming matters — users expect tokens to appear as they're generated, not all at once after a multi-second delay. Next.js Route Handlers support streaming natively via the Web Streams API.
export async function POST(req) {
const { messages } = await req.json();
const upstream = await fetch("https://api.subtoapi.app/v1/messages", {
method: "POST",
headers: {
"Content-Type": "application/json",
"Authorization": `Bearer ${process.env.SUBTOAPI_KEY}`,
},
body: JSON.stringify({
model: "claude-sonnet-4",
max_tokens: 1024,
stream: true,
messages,
}),
});
return new Response(upstream.body, {
headers: { "Content-Type": "text/event-stream" },
});
}
On the client, read the stream with ReadableStream and append chunks to state as they arrive, or use a library like ai (Vercel's SDK) or eventsource-parser to handle server-sent events cleanly. Full event formats are documented at /docs/streaming.
Handling tool use in a Next.js route
If your app needs Claude to call functions — looking up a database record, hitting an internal API, fetching live data — tool use requires an extra round trip: Claude returns a tool_use block, your server executes it, and you send the result back in a follow-up request.
const tools = [
{
name: "get_order_status",
description: "Look up the status of an order by ID",
input_schema: {
type: "object",
properties: { order_id: { type: "string" } },
required: ["order_id"],
},
},
];
Pass tools alongside messages in your request body, check the response for stop_reason: "tool_use", execute the matching function in your route handler, and append the result as a tool_result message before calling the API again. See /docs/tools for schema details and examples.
Choosing direct Anthropic access vs. a gateway
Calling Anthropic's API directly from Next.js works fine for a single project. Where it gets harder is managing multiple environments, tracking per-feature usage, rotating keys across a team, or giving teammates access without sharing one root credential.
SubToAPI sits between your Next.js app and Claude, giving you application-scoped keys (sub_live_...), usage metadata per request, and a dashboard for managing seats across a team — without changing your request format, since it mirrors the standard Messages API. If you're building something beyond a solo side project, start with a free trial at /signup and swap one environment variable.
Project structure tips
- Keep all Claude calls inside
app/api/*/route.tsfiles — never in client components orgetServerSidePropson the client path. - Centralize your fetch logic in a small
lib/claude.tshelper so retry logic and error handling live in one place. - Set
max_tokensdeliberately per route — chat UIs and summarization routes have very different needs. - Log request IDs and status codes server-side so you can debug failed calls without exposing stack traces to users.
For a minimal working example end to end, /docs/quickstart covers the request/response cycle in under 10 lines of code.
Questions
Do I need a backend separate from Next.js to use the Claude API? No. Next.js Route Handlers (or API Routes in the Pages Router) are a full backend runtime — they're sufficient for calling Claude, handling streaming, and managing tool use without a separate server.
Can I stream Claude responses to the browser with Next.js? Yes. Route Handlers support the Web Streams API natively, so you can forward Claude's server-sent events directly to the client and render tokens as they arrive.
Should I call Anthropic directly or use a gateway like SubToAPI? Direct calls work for single-developer projects. If you need per-app API keys, team seats, or usage tracking across multiple features, a gateway like SubToAPI adds that without changing your existing request format — check /pricing for plan details.