Anthropic API Next.js Tutorial | Coding Capybaras

A non-technical founder's Anthropic API Next.js tutorial: add an AI feature with streaming, rate limits, and cost control — the same steps work for the OpenAI API.

· Justin Boggs

Macro photograph of a dark circuit board with fine copper traces

Photo by Alexandre Debiève on Unsplash

To add an AI feature to your Next.js SaaS with the Anthropic or OpenAI API, you route every request through a server-side route handler that holds your secret key, stream the response back so the UI feels alive, and put authentication plus a rate limit in front of it so one user can't drain your budget. That's the whole shape of it. The model provider — Claude or GPT — is almost interchangeable; the wiring, the guardrails, and the cost discipline are what actually matter, and they're the same either way. This tutorial walks the wiring end to end, in the order you should build it.

TL;DR

  • Call the AI API from a server-side route handler, never the browser — your API key must never ship in frontend code.
  • Stream the response so users see text appear immediately instead of staring at a spinner.
  • Put authentication and a rate limit in front of the endpoint before you launch. This is non-optional for cost safety.
  • Start on a small, cheap model while building; reserve the expensive model for the cases that actually need it.
  • Anthropic and OpenAI have near-identical integration shapes — the guardrails below apply to both.

Step 1: Decide what the feature does before you write a line

The most expensive mistake with AI features isn't technical — it's building an open-ended chatbot when your product needed one narrow, reliable action. Before any code, write one sentence: "This feature takes ___ and returns ___." A summarizer takes a block of text and returns three bullet points. A tagger takes a support ticket and returns a category. A draft-writer takes a few fields and returns a first draft.

Narrow features are cheaper, easier to make reliable, and far easier to keep from going off the rails. They also map cleanly onto a good prompt, which is its own skill — the same prompt-engineering patterns that ship working code apply to the prompts your product sends on a user's behalf. A vague "chat with our docs" feature invites vague, costly, unpredictable calls. A specific "summarize this ticket in three bullets" feature is a fixed input, a fixed output, and a predictable bill.

Write that one sentence down. It becomes the system prompt, it defines what you test, and it tells you which model tier you need. Everything downstream gets easier when the job is small and named.

Step 2: Put the API key on the server, never the browser

Here is the single most important rule in this entire tutorial, and the one AI assistants sometimes get wrong: your provider API key lives only on the server, and it must never be exposed to the browser.

In Next.js, any environment variable prefixed with NEXT_PUBLIC_ gets baked directly into the JavaScript that ships to the user's browser — where anyone can read it in DevTools. So your key goes in .env.local as ANTHROPIC_API_KEY (or OPENAI_API_KEY), with no NEXT_PUBLIC_ prefix. It is read only inside server code. If your AI assistant ever writes NEXT_PUBLIC_ANTHROPIC_API_KEY, stop it — that leaks a billable secret to the world.

This is exactly why every AI call goes through a route handler — a small piece of backend code at a URL like /api/summarize. The browser calls your route handler; your route handler calls Anthropic or OpenAI with the secret key; the answer flows back. The key never leaves the server. This is the same discipline behind keeping secrets out of your frontend and out of git generally — treat a leaked AI key like a leaked Stripe key, because the fraud potential is similar.

A minimal Anthropic route handler looks like this:

// app/api/summarize/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 { text } = await req.json();

  const message = await anthropic.messages.create({
    model: "claude-haiku-4-5-20251001",
    max_tokens: 400,
    system: "Summarize the user's text in exactly three bullet points.",
    messages: [{ role: "user", content: text }],
  });

  return Response.json({ summary: message.content });
}

That works, but it makes the user wait for the entire answer before anything appears. For anything longer than a sentence, you want streaming — which is the next step.

Step 3: Stream the response so the UI feels alive

Streaming is the difference between a feature that feels broken and one that feels magical. Instead of the user watching a spinner for eight seconds and then getting a wall of text, the words appear as the model generates them — the "typewriter" effect you've seen in ChatGPT and Claude.

Both providers stream over server-sent events (SSE). Per Anthropic's streaming documentation, when you set stream: true the API emits a structured sequence of typed events: a message_start, then for each block a content_block_start, a run of content_block_delta events, and a content_block_stop, closing with message_delta and message_stop. The actual words live in the content_block_delta events — specifically the text_delta inside each one. You don't have to memorize that; the SDK's helpers pull the text out for you. But it's worth knowing the shape, because when you're reading AI code output you didn't write, recognizing these event names tells you the streaming is wired correctly.

The SDK makes streaming a few lines. Anthropic's TypeScript SDK exposes a stream helper with a text_stream you can pipe straight to the browser:

// app/api/summarize/route.ts (streaming)
import Anthropic from "@anthropic-ai/sdk";

const anthropic = new Anthropic({ apiKey: process.env.ANTHROPIC_API_KEY });

// Allow the stream up to 30 seconds to finish
export const maxDuration = 30;

export async function POST(req: Request) {
  const { text } = await req.json();

  const stream = anthropic.messages.stream({
    model: "claude-haiku-4-5-20251001",
    max_tokens: 400,
    system: "Summarize the user's text in exactly three bullet points.",
    messages: [{ role: "user", content: text }],
  });

  return new Response(stream.toReadableStream());
}

Anthropic strongly recommends using the official SDKs for streaming rather than parsing the raw SSE events yourself, and for a non-technical founder that advice is doubly true — let the maintained library handle the event plumbing. If you'd rather stay provider-agnostic, the Vercel AI SDK wraps both Anthropic and OpenAI behind one streamText call and a toDataStreamResponse(), so you can swap models later without rewriting the frontend. Either path is fine; pick one and move on.

Step 4: Gate it behind auth and a rate limit — before you launch

This is the step founders skip and regret. An AI endpoint without a rate limit is a hole in the bottom of your bank account. As Vercel's guide to securing AI apps puts it, the biggest concern with an AI application is abuse — bad actors hammering your endpoint and running up your bill. Rate limiting is the defense: it caps how many requests a single client can make in a given window.

Two gates, in order:

  • Authentication. The AI endpoint should require a logged-in user. An anonymous, public AI route is an open invitation for someone to script thousands of calls overnight. Tie the route to your existing auth so every call has a real user attached — the same auth layer you already use for the rest of the app.
  • A per-user rate limit. Even authenticated, one user shouldn't be able to fire 500 requests a minute. Limit by user ID — a common pattern is a sliding window in Redis (Upstash has a free tier that runs on Vercel's edge). Vercel's WAF can also apply a rate-limit rule directly to the route path if you'd rather not write the code.

Rate limiting does double duty. Beyond blocking abuse, it's how you implement usage tiers: free users get a small daily allotment of AI calls, paying users get more. That turns a cost center into a reason to upgrade — the same instinct behind usage-based billing, applied to the most expensive thing your app does.

The sequence, once both gates are in, looks like this:

sequenceDiagram
    participant U as User's browser
    participant R as Route handler (/api/summarize)
    participant L as Auth + rate limit
    participant A as Anthropic / OpenAI API
    U->>R: POST text to summarize
    R->>L: Is this a logged-in user under their limit?
    L-->>R: Allowed
    R->>A: Call model with secret key (server-side)
    A-->>R: Stream text_delta events
    R-->>U: Stream words back to the UI

Notice the key never touches the user's browser, and no request reaches the paid API until it's cleared auth and the rate limit. That ordering is the whole game.

Step 5: Control cost with model choice and caps

AI features are the one part of your app where usage directly equals dollars, so a little discipline here pays off every month. Three habits keep the bill sane:

  • Start small. Build and test on the cheapest capable model — Claude Haiku or GPT's small tier. Only reach for the flagship model on the specific calls that genuinely need deeper reasoning. Most product features (summaries, tagging, extraction, short drafts) run fine on the small tier, and the price gap between tiers is large.
  • Cap the output. Set max_tokens to the smallest value that fits the job. A three-bullet summary doesn't need a 4,000-token ceiling. Output tokens usually cost more than input tokens, so capping length is a direct cost lever.
  • Set budget alerts. Both providers let you set usage alerts in the dashboard. Turn them on the day you launch, not the day you get a surprise invoice.

Keep an eye on this the way you'd watch any hidden infrastructure cost — AI spend is unusually spiky because it scales with user behavior, not with a fixed server. And instrument it: log how many AI calls each user makes and pipe the counts into your analytics, so you can see cost-per-user before it becomes a problem rather than after.

Step 6: Handle the failures that will happen

AI calls fail more often than a normal database query, and in more interesting ways, so a little error handling separates a feature that feels solid from one that feels flaky. Three failures are worth planning for specifically.

The first is rate limiting from the provider itself. Separate from the limit you put on your own users, Anthropic and OpenAI cap how fast your account can call them, and when you exceed it they return a 429 response. OpenAI's rate-limits documentation spells out these ceilings and the headers that report them. The right response to a 429 is not to crash — it's to wait a moment and retry, ideally with a short backoff. The official SDKs retry transient errors for you, which is one more reason to use them rather than calling the raw API.

The second is a slow or hung response. Models occasionally take longer than expected, and a request that never resolves ties up a serverless function until it times out. Your maxDuration cap protects the server, but the frontend should also show the user a clear "this is taking longer than usual" state rather than an eternal spinner. Streaming helps here too: if words are arriving, the user knows it's working.

The third is a bad or empty answer. Models sometimes return something malformed, off-topic, or blank. For narrow features, validate the output before you show it — if you asked for three bullets and got prose, either retry once or fall back to a plain message. This is the same defensive instinct as reviewing AI output you didn't write: don't assume the response is well-formed just because the call succeeded. Log every failure so you can see patterns, the way you'd wire up error tracking for the rest of the app.

Anthropic vs OpenAI: which to reach for

For the integration itself, the two are close enough that you can pick on preference and switch later. Here's the honest side-by-side:

| Consideration | Anthropic (Claude) | OpenAI (GPT) | | --- | --- | --- | | Official TypeScript SDK | @anthropic-ai/sdk | openai | | Streaming | SSE, messages.stream() helper | SSE, streaming completions | | Small/cheap tier for dev | Claude Haiku | GPT small tier | | Provider-agnostic option | Vercel AI SDK wraps it | Vercel AI SDK wraps it | | Rate-limit / cost guidance | Dashboard alerts, max_tokens | Dashboard alerts, documented rate limits | | Integration shape | Route handler + stream + guardrails | Route handler + stream + guardrails |

The last row is the point: the shape is identical. Route handler, streaming, auth, rate limit, cost caps. If you build the guardrails well, moving from one provider to the other — or supporting both and picking per feature — is a small change, especially if you went through the Vercel AI SDK. Don't agonize over the provider. Get the wiring and the guardrails right, and the model is a swappable part.

Frequently asked questions

Do I need to stream, or can I just return the full response?

You can return the full response, and for very short outputs (a single tag or category) it's fine. But for anything a user reads — summaries, drafts, explanations — streaming dramatically improves the perceived speed. It's a few extra lines with the SDK, so it's usually worth doing from the start.

Where exactly does the API key go?

In .env.local as ANTHROPIC_API_KEY or OPENAI_API_KEY, with no NEXT_PUBLIC_ prefix, and it's read only inside your server-side route handler. Never put it in frontend code, and never commit .env.local to git. A leaked AI key can be abused for real money, just like a leaked payment key.

How do I stop one user from running up a huge bill?

Two layers: require authentication on the AI route, then apply a per-user rate limit (a sliding-window limit in Redis, or a Vercel WAF rule on the route path). Build both before launch — an ungated AI endpoint is the single most common way indie founders get a shocking invoice.

Which model should I start with?

The cheapest capable one — Claude Haiku or GPT's small tier — for both development and most production features. Only escalate to a flagship model for calls that genuinely need deeper reasoning. The quality is often indistinguishable for narrow tasks, and the cost difference is significant.

Can my AI assistant write this whole integration for me?

Mostly yes, and it's a great use of an AI coding tool — but you have to check the guardrails yourself. The two things to verify by hand: the key has no NEXT_PUBLIC_ prefix and never reaches the browser, and the route has auth plus a rate limit. Those are exactly the parts an assistant can quietly skip, so treat them as your manual review checklist.

Does this work the same in the Coding Capybaras boilerplate?

Yes. The route-handler pattern, server-only secrets, and auth layer are already in place, so adding an AI feature is mostly writing the handler and the prompt on top of infrastructure that already exists.

The bottom line

Adding an AI feature with the Anthropic or OpenAI API comes down to five moves: name the narrow job, keep the key server-side in a route handler, stream the answer, gate the endpoint with auth and a rate limit, and control cost with a small model and tight caps. Do those, and you have a feature that feels fast, stays safe, and won't surprise you on the invoice — regardless of which provider you chose. The model is the easy, swappable part. The guardrails are the product.

If you're wiring AI into a SaaS with an AI coding tool, the Coding Capybaras marketplace has copy-paste prompts that scaffold this exact pattern — route handler, server-only key, auth, and rate limiting — into a Next.js + Supabase + Stripe app.