Claude API Next.js Integration Tutorial
Integrating Claude into a Next.js App
If you're searching for a Claude API Next.js integration tutorial, you're probably trying to add AI chat, content generation, or an assistant feature to an app built with the App Router or Pages Router, and you want to know where the API calls should live and how to keep your key off the client.
The short answer: Claude calls must happen on the server. You never call Anthropic's API (or any LLM provider) directly from client-side JavaScript, because that exposes your API key in the browser. In Next.js this means using Route Handlers (App Router) or API Routes (Pages Router) as a proxy between your frontend and Claude. This guide walks through both a direct integration and a simplified version using an API wrapper.
Project setup
Start with a standard Next.js app:
npx create-next-app@latest claude-chat
cd claude-chat
Add your API key to .env.local. Never commit this file:
ANTHROPIC_API_KEY=sk-ant-xxxxxxxxxxxx
Building the API route (App Router)
Create app/api/chat/route.js:
export async function POST(req) {
const { messages } = await req.json();
const response = await fetch('https://api.anthropic.com/v1/messages', {
method: 'POST',
headers: {
'content-type': 'application/json',
'x-api-key': process.env.ANTHROPIC_API_KEY,
'anthropic-version': '2023-06-01',
},
body: JSON.stringify({
model: 'claude-sonnet-4-5',
max_tokens: 1024,
messages,
}),
});
const data = await response.json();
return Response.json(data);
}
This route receives messages from your frontend, forwards them to Claude, and returns the response. The key stays server-side the entire time.
Calling it from a client component
'use client';
import { useState } from 'react';
export default function Chat() {
const [input, setInput] = useState('');
const [reply, setReply] = useState('');
async function sendMessage() {
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);
}
return (
<div>
<input value={input} onChange={(e) => setInput(e.target.value)} />
<button onClick={sendMessage}>Send</button>
<p>{reply}</p>
</div>
);
}
This pattern works identically in the Pages Router — just move the handler logic into pages/api/chat.js and export a default function that checks req.method.
Streaming responses in Next.js
A chat UI feels far more responsive when tokens stream in instead of appearing all at once. Claude's API supports stream: true, and Next.js Route Handlers can return a ReadableStream directly:
export async function POST(req) {
const { messages } = await req.json();
const upstream = await fetch('https://api.anthropic.com/v1/messages', {
method: 'POST',
headers: {
'content-type': 'application/json',
'x-api-key': process.env.ANTHROPIC_API_KEY,
'anthropic-version': '2023-06-01',
},
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' },
});
}
On the client, read the stream with ReadableStream reader or use the EventSource/fetch streaming pattern to append chunks as they arrive. This gives you a ChatGPT-style typing effect without any extra libraries.
Handling errors and rate limits
Production integrations need to handle rate limiting (429), overload errors (529), and malformed requests (400) gracefully. Wrap the upstream call in a try/catch and return a normalized error shape to your frontend:
try {
const upstream = await fetch(/* ... */);
if (!upstream.ok) {
const err = await upstream.json();
return Response.json({ error: err }, { status: upstream.status });
}
// success path
} catch (e) {
return Response.json({ error: 'Network error' }, { status: 500 });
}
Log these errors server-side so you can tell the difference between a bad prompt, a quota issue, and an outage.
A simpler path: skip the proxy boilerplate
The pattern above works, but you end up rebuilding the same plumbing in every project: a route handler, error normalization, usage tracking, and key rotation if you ever add teammates. SubToAPI turns your Claude access into a hosted HTTPS API with its own scoped keys (sub_live_...), so your Next.js route handler becomes a thin pass-through instead of a full integration layer:
export async function POST(req) {
const { messages } = await req.json();
const res = 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-5',
max_tokens: 1024,
messages,
}),
});
return Response.json(await res.json());
}
Because the key is scoped to your application rather than your personal Anthropic account, you can rotate it, set per-key limits, and share access with teammates without exposing the underlying credential. Streaming, tool use, and usage metadata follow the same request shape, so the Next.js code you already wrote for a direct integration mostly carries over — see the quickstart and the streaming guide for the exact payloads.
Which approach should you pick?
- Direct integration (calling
api.anthropic.comyourself) is fine for a solo project where you're the only one managing keys and usage. - A hosted layer like SubToAPI makes sense once you need team seats, per-key usage visibility, or you want one dashboard instead of managing environment variables across multiple projects and deployments. Plans start at €9/month for solo use, with team pricing at €19 and €49 per seat, and there's a free trial at signup.
Either way, the Next.js side of the integration — route handlers, streaming, and error handling — stays the same.
Questions
Do I need a backend to use Claude in Next.js, or can I call it from the client? You need a server-side route. Calling Claude directly from client-side JavaScript would expose your API key in the browser, so Next.js Route Handlers or API Routes act as the required proxy layer.
Does streaming work the same way in the Pages Router as the App Router? Yes, conceptually. The App Router returns a ReadableStream from a Route Handler; the Pages Router streams by writing chunks directly to the res object in an API route. The upstream Claude request is identical in both cases.
Can I use the same Next.js code with SubToAPI instead of Anthropic directly? Mostly yes — you swap the endpoint and Authorization header. The request and response shapes follow the same /v1/messages format described in the docs, so existing route handler logic needs minimal changes.