Skip to main content
Jodei
Insights

Prompt Caching with the Claude API: Cut LLM Costs in Next.js

Claude's prompt caching pins your system prompt tokens in its KV store so repeat requests pay 10x less for that prefix. Here's how to configure it in a Next.js 16 route handler and when it's worth the setup cost.

6 min read

Every request to an LLM re-ingests the same system prompt. For a short prompt that costs almost nothing — but for a RAG application where the system message includes 50 pages of documentation, or a customer-support bot with 200 few-shot examples, that repeated ingestion adds up fast. Claude's prompt caching feature lets you pay for that prefix once, then read it back at a fraction of the cost on every subsequent request.

How prompt caching works

When you mark a message block with cache_control: { type: "ephemeral" }, Claude pins the KV representation of everything up to that point in its inference stack. The next request that sends the same prefix — same content, same position — is served from that cache entry instead of re-encoding the tokens.

Two cost tiers apply. A cache write costs 1.25× the normal input token price — slightly more than a plain request, because the server has to store the state. A cache read costs 0.1× — ten times cheaper. After a modest number of requests the savings dominate, and at the scale of a production AI feature the difference is significant.

Cache entries are ephemeral: they expire after five minutes of inactivity. Each cache hit resets the clock, so a system prompt used on every request will stay warm indefinitely. A prompt used only once an hour will not — factor that into your decision of what to cache.

Enabling it in a Next.js 16 route handler

The Anthropic SDK accepts cache_control directly on content blocks. In a Next.js 16 Route Handler, the setup looks like this:

// src/app/api/chat/route.ts
import Anthropic from "@anthropic-ai/sdk";
import { NextRequest } from "next/server";

const client = new Anthropic();

const SYSTEM_PROMPT = `You are a senior web developer specialised in Next.js and AI-native
applications. [... many paragraphs of context, guidelines, and examples ...]`;

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

  const response = await client.messages.create({
    model: "claude-opus-5",
    max_tokens: 1024,
    system: [
      {
        type: "text",
        text: SYSTEM_PROMPT,
        cache_control: { type: "ephemeral" },
      },
    ],
    messages,
  });

  return Response.json(response);
}

The cache_control block goes on the last content block you want included in the cached prefix. Everything up to and including that block is cached; everything after it (the live user messages) is not.

Caching RAG documents alongside the system prompt

The most impactful use of prompt caching is pairing a large system prompt with retrieved documents. You can stack cache_control markers at multiple points to cache in layers:

const response = await client.messages.create({
  model: "claude-opus-5",
  max_tokens: 2048,
  system: [
    {
      type: "text",
      text: SYSTEM_PROMPT,
      cache_control: { type: "ephemeral" }, // cache the base system prompt
    },
    {
      type: "text",
      text: retrievedDocuments,             // RAG context fetched per-request
      cache_control: { type: "ephemeral" }, // also cache this if it's stable
    },
  ],
  messages,
});

A separate cache entry is created for each unique prefix. If the retrieved documents change between requests (different user queries, different retrieval results), you get a cache miss at that layer. When the same documents appear across many requests — a shared knowledge base, a product catalog — the second marker pays off too.

Reading cache statistics from the response

The API response includes a usage field with per-request cache metrics. Log these to confirm the cache is working:

const { usage } = response;
console.log({
  inputTokens: usage.input_tokens,
  cacheCreationTokens: usage.cache_creation_input_tokens,
  cacheReadTokens: usage.cache_read_input_tokens,
  outputTokens: usage.output_tokens,
});
// First request:   { inputTokens: 0, cacheCreationTokens: 4200, cacheReadTokens: 0, ... }
// Second request:  { inputTokens: 0, cacheCreationTokens: 0,    cacheReadTokens: 4200, ... }

If cacheReadTokens is zero on the second request, the prefix did not match — check that the system prompt content is identical across requests and that no serialisation is introducing whitespace or ordering differences.

When to reach for it

Prompt caching pays off when the cacheable prefix is large and reused often. The breakeven point is roughly eight cache reads per write at current Claude pricing — at that point the savings on reads offset the 25% write surcharge. A 4 000-token system prompt used on every user request in a production app will cross that threshold within seconds of the cache being warm.

The cases where it adds little: short system prompts that are already cheap to ingest; prompts that vary per request (dynamic user context injected at the system level); or low-traffic routes where the cache expires between requests. In those cases, skip the cache_control block — the API behaves identically without it, and you avoid the write surcharge.

The short version: if your system prompt is longer than a few hundred tokens and your route sees steady traffic, add cache_control to the system block, ship it, and watch the token cost drop.