How to Implement Claude API in a Next.js App
If you're asking how to implement the Claude API in a Next.js app, the short answer is: never call Claude directly from the browser. Create a server-side route handler in Next.js that holds your API key, forwards the request to Claude, and returns the response to your client component. This keeps your key out of the browser bundle and gives you a place to add rate limiting, logging, and error handling.
This guide walks through the actual implementation: project setup, a working route handler using the App Router, a client component that calls it, streaming responses token-by-token, and a few production details people usually get wrong the first time.
Why you need a server route, not a client-side call
Claude's API requires an API key in the request headers. If you put that key in client-side JavaScript, anyone who opens dev tools can copy it and run up your bill. Next.js solves this cleanly with route handlers (in the App Router, app/api/.../route.ts) that run only on the server. The browser talks to your route, your route talks to Claude.
This also gives you a single choke point to add things like per-user rate limits, request logging, or swapping providers later without touching your frontend code.
Project setup
Start with a standard Next.js App Router project:
npx create-next-app@latest claude-nextjs-demo
cd claude-nextjs-demo
Add your key to .env.local:
ANTHROPIC_API_KEY=sk-ant-...
Never commit .env.local. If you're deploying to Vercel, add the same variable in your project's environment settings.
Writing the route handler
Create app/api/chat/route.ts:
export async function POST(req: Request) {
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,
}),
});
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 plain, non-streaming implementation. It's a good starting point because it's easy to debug — you get the full JSON response back and can inspect it in the Network tab before adding complexity.
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 send() {
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={send}>Send</button>
<p>{reply}</p>
</div>
);
}
This pattern scales to real chat UIs — just append each turn to a messages array stored in component state and send the whole history on every request.
Adding streaming
For anything conversational, non-streaming responses feel slow because the user waits for the entire reply before seeing anything. Claude supports streaming via server-sent events, and Next.js route handlers can proxy that stream directly:
export async function POST(req: Request) {
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 the ReadableStream reader API or a small SSE-parsing helper, and append chunks to your UI state as they arrive. This is the single biggest UX improvement you can make to a Claude-powered Next.js app.
Tool use and structured output
If your app needs Claude to call functions — looking up data, running calculations, triggering actions in your app — you define tool schemas in the request body and handle the tool_use blocks Claude returns. The route handler pattern above doesn't change; you just add a tools array to the request and a loop in your server code that executes the tool, then sends the result back to Claude in a follow-up message. This gets verbose fast if you're building it from scratch for several tools.
Simplifying the backend with SubToAPI
Writing and maintaining a raw proxy route works fine for a single project, but it gets repetitive once you have multiple apps, need usage tracking per user, or want teammates to have their own keys without sharing one root credential. SubToAPI turns your existing Claude access into a standard HTTPS API with scoped application keys (sub_live_...), so your Next.js route handler calls one consistent endpoint regardless of which underlying account or plan is behind it:
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-5",
max_tokens: 1024,
messages,
}),
});
Streaming, tool use, and usage metadata work the same way as the direct API — see the quickstart, messages, streaming, and tools docs. Plans start at €9/month for solo projects, with team seats on the Team and Scale plans if multiple people need their own keys under one account. You can try it with a free trial at signup; pricing details are on the pricing page.
Deployment notes
A few things to check before shipping:
- Set the runtime explicitly if you need long streaming responses — Next.js route handlers default to the Node.js runtime unless you export
runtime = "edge". - Validate and sanitize the
messagesarray server-side; don't trust whatever shape the client sends. - Add basic rate limiting on the route handler, not just in the UI, since the UI can always be bypassed.
- Log token usage per request if you're billing users or tracking costs — this is also something a managed layer like SubToAPI handles automatically.
questions
Do I need the Vercel AI SDK to use Claude in Next.js? No. It's a helpful convenience library for streaming and chat state management, but a plain fetch call inside a route handler is enough to implement the Claude API in Next.js, as shown above.
Should I call Claude from a Server Component or a route handler? Use a route handler for anything interactive (chat, streaming). Server Components are fine for one-off server-rendered calls where the result doesn't need to update without a page reload.
How do I keep my Claude API key safe in a Next.js app? Store it in environment variables, read it only inside server-side code (route handlers or Server Components), and never pass it to the client. If you need per-app or per-user keys, a service like SubToAPI gives each app its own scoped key instead of one shared secret.