← Blog

Set Up Claude API in a Next.js App: Full Guide

2026-10-09 · 4 min read · SubToAPI Team

Setting up Claude in a Next.js app means wiring a server-side API route that calls Claude, keeping your API key off the client, and exposing a clean endpoint your frontend can call for chat, completions, or tool use. The short version: install the SDK, store the key in .env.local, create a route handler under app/api/, and fetch it from a client component with fetch or EventSource for streaming.

This guide walks through the full setup, including streaming responses, handling environment variables correctly in both local dev and production (Vercel, Docker, or self-hosted), and what changes if you're using a provider like SubToAPI instead of raw Anthropic credentials.

Why Claude calls must go through a server route

Next.js apps run code in two places: the browser and the server. Any API key placed in client-side code — even inside a "use client" component — ends up in the bundle and is visible to anyone who opens dev tools. Claude API keys (and SubToAPI keys, which look like sub_live_...) must only ever be used in server code: Route Handlers, Server Actions, or getServerSideProps/middleware if you're on the Pages Router.

The pattern is always:

  1. Browser sends a request to your own /api/chat route.
  2. Your Next.js server calls Claude (or SubToAPI) with the real key.
  3. The response (streamed or not) is relayed back to the browser.

Step 1: Install dependencies and set environment variables

npm install @anthropic-ai/sdk

If you're using SubToAPI, no SDK is required — it's a plain HTTPS API, so fetch works fine. Either way, add your key to .env.local:

ANTHROPIC_API_KEY=sk-ant-...
# or, if using SubToAPI
SUBTOAPI_KEY=sub_live_...

Add .env.local to .gitignore (Next.js does this by default) and set the same variable in your hosting dashboard for production.

Step 2: Create the API route

In the App Router, create 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-opus-4-20250514",
    max_tokens: 1024,
    messages,
  });

  return Response.json(response);
}

If you're routing through SubToAPI instead, the setup is simpler — one fetch, no SDK version to track:

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-opus-4-20250514",
      max_tokens: 1024,
      messages,
    }),
  });

  const data = await res.json();
  return Response.json(data);
}

Full request/response shapes are documented at /docs/messages. The point of this layer is that your frontend code doesn't know or care whether the key behind it is a raw Anthropic key or an application key with its own rate limits, usage tracking, and team seat assigned to it.

Step 3: Call the route from a client component

"use client";
import { useState } from "react";

export default function Chat() {
  const [reply, setReply] = useState("");

  async function send(prompt: string) {
    const res = await fetch("/api/chat", {
      method: "POST",
      body: JSON.stringify({
        messages: [{ role: "user", content: prompt }],
      }),
    });
    const data = await res.json();
    setReply(data.content[0].text);
  }

  return (
    <button onClick={() => send("Explain closures in JS")}>
      Ask Claude
    </button>
  );
}

Step 4: Add streaming for better UX

Non-streamed responses feel slow for anything over a few hundred tokens. Next.js Route Handlers support streaming natively via ReadableStream, which pairs well with Claude's SSE-based streaming:

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-opus-4-20250514",
      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 fetch streaming API or a small helper that parses data: lines and appends tokens as they arrive. Streaming details and event types are covered in /docs/streaming.

Step 5: Tool use and other request options

If your Next.js app needs Claude to call functions — database lookups, calculators, internal APIs — pass a tools array in the same request body and handle tool_use blocks in the response before sending results back in a follow-up message. This works identically whether you call Anthropic directly or through a gateway; see /docs/tools for the request format and response handling.

Choosing between a direct key and a gateway

Calling Anthropic directly works fine for a single-developer project. It gets harder once you need:

SubToAPI sits between your Next.js app and Claude, issuing scoped sub_live_... application keys with streaming, tool use, usage metadata, and team seats managed from one dashboard. You get the same request/response shape shown above, so migrating an existing Next.js integration is usually a one-line change to the base URL and header. Start at /signup, check /pricing for Solo, Team, and Scale plans, or jump straight to /docs/quickstart for a working example.

questions

Can I call Claude directly from a Next.js Client Component? No — doing so exposes your API key in the browser bundle. Always route the request through a server-side API route or Server Action, and only call Claude from there.

Does the App Router or Pages Router matter for this setup? Not much. App Router uses Route Handlers (app/api/.../route.ts); Pages Router uses pages/api/*.ts files with req/res. The Claude request logic is identical either way.

How do I keep the API key safe across environments? Use .env.local for local development and your hosting provider's environment variable settings for staging/production, and never commit the file. If you use SubToAPI, you can also issue separate keys per environment from the dashboard for easier revocation.

Turn your Claude access into an HTTPS API

SubToAPI gives you application API keys, streaming, tool use and usage insights on top of your existing Claude access — set up in minutes.

Start free  Read the quickstart →