Sanity API bill spiking? Why requests explode on Next.js sites

By Nayan Kyada · · 6 min read

Part of The Sanity + Next.js Guide

If your Sanity invoice or usage warning jumped and nobody changed the content volume, the cause is almost always how the Next.js front end talks to the API. A Sanity API bill spike is rarely about editors; it is about requests per page view. This post covers the symptoms, what leaving it alone costs, and the fix path I follow.

Symptoms of a Sanity API bill spike

The pattern is consistent across sites like this:

  • A usage email or dashboard warning about API requests or CDN requests, with no matching traffic growth.
  • Request counts that climb on days with a crawler spike or an email campaign.
  • Pages that feel fine but the Studio or the site occasionally returns rate-limit errors.
  • A Vercel bill that rose at the same time, because uncached pages also burn function time.

Open the usage section in your Sanity project management page and compare it with your hosting analytics. If API requests grow faster than page views, the site is making too many calls per visit. Plan limits and overage rules change, so confirm the current numbers on the Sanity pricing breakdown and Sanity's own pricing page before you draw conclusions.

What it costs to ignore it

The direct cost is overage or a forced plan upgrade. The indirect costs are worse:

  • Slow pages, because every uncached request adds a network round trip to the render. I covered the diagnosis side in Sanity website slow? How to find the cause.
  • Rate limiting under load, which shows up as failed builds or blank sections exactly when traffic peaks.
  • A false belief that Sanity is expensive, which sometimes triggers a pointless migration.

Buying a bigger plan hides the problem until the next traffic jump. Fixing the request pattern is usually a few days of work, not a rebuild.

Cause 1: the client bypasses the CDN

The most common finding is a client configured with the CDN turned off everywhere, often copied from a preview setup and never changed back.

// src/sanity/lib/client.ts (broken pattern)
import { createClient } from 'next-sanity'
 
export const client = createClient({
  projectId: process.env.NEXT_PUBLIC_SANITY_PROJECT_ID!,
  dataset: process.env.NEXT_PUBLIC_SANITY_DATASET!,
  apiVersion: '2025-01-01',
  useCdn: false, // every published read hits the live API
})

Published content does not need to be live to the millisecond. Use the CDN for it and keep a separate path for drafts.

// src/sanity/lib/client.ts (fixed)
import { createClient } from 'next-sanity'
 
export const client = createClient({
  projectId: process.env.NEXT_PUBLIC_SANITY_PROJECT_ID!,
  dataset: process.env.NEXT_PUBLIC_SANITY_DATASET!,
  apiVersion: '2025-01-01',
  useCdn: true,
})
 
export async function fetchPublished(query: string, params: Record<string, unknown> = {}, tags: string[] = []) {
  return client.fetch(query, params, {
    next: { revalidate: 3600, tags },
  })
}

The tags argument matters for the next cause.

Cause 2: no Next.js caching or tag revalidation

If pages render dynamically on every request, each visit costs one or more Sanity calls. Bots multiply that. The fix is to cache fetches, tag them by document type, and revalidate from a webhook when content changes. The webhook side is written up in Sanity webhooks with ISR revalidation, and for busy publishing days see batching tags so ten edits do not trigger ten rebuild storms.

Check for accidental opt-outs too: reading cookies or headers in a layout, or passing cache: 'no-store' in a shared fetch helper, makes the whole route dynamic.

Cause 3: draft mode and preview leaking into production

Preview setups sometimes poll or use a non-CDN token client on every page, not just inside draft mode. Confirm that the token-bearing client is only used when draft mode is enabled and never ships to the browser. If you run live preview, scope it to the Presentation tool sessions. See draft mode without sacrificing edge performance for the pattern.

Cause 4: heavy queries and N+1 fetching

One page making six separate fetches, or a list component that fetches per card, multiplies every view. Collapse them into one GROQ query with a projection that returns only what the page renders.

*[_type == "post" && defined(slug.current)] | order(publishedAt desc)[0...12]{
  title,
  "slug": slug.current,
  publishedAt,
  "author": author->name,
  "image": mainImage.asset->{url, metadata{dimensions, lqip}}
}

The related reading is avoiding N+1 queries with Sanity references. Also check generateStaticParams: a build that fetches the full document for every path, repeated by several workers, can generate a surprising number of requests per deploy.

Cause 5: bots, previews and image URLs

Crawlers hit uncached routes such as search pages, filter combinations, and paginated archives with unbounded parameters. Cache or block those, and add a sensible robots rule. For images, serving original uploads at full size inflates CDN traffic. Use next/image with proper sizes so the browser requests the right width instead of the original.

The fix path

  1. Pull usage numbers from Sanity and traffic from your host, and compare the ratio over the last few weeks.
  2. Search the codebase for useCdn: false, no-store, and revalidate = 0.
  3. Route all published reads through one cached fetch helper with tags.
  4. Wire a webhook that revalidates by tag on publish.
  5. Merge multi-fetch pages into single projected queries.
  6. Block or cache crawler traps.
  7. Re-measure after a week. The request-per-pageview ratio should fall sharply.

What to ask a developer

  • How many Sanity requests does one page view cost today, and how will you measure it?
  • Which routes are dynamic, and why?
  • Will content still update within a minute of publishing after the caching changes?
  • Do you keep drafts and published reads on separate clients?
  • Can you show before and after request counts from the dashboard?

Be cautious about anyone whose first answer is to switch CMS. The API pattern is the problem, not the platform.

Work I do

This kind of diagnosis and cleanup is work I do as a fixed-scope Performance and Sanity audit, from $800, and the findings usually tell you whether a small fix or a bigger one is needed. For what an audit covers, see what a paid Next.js audit includes.

Frequently asked questions

Why is my Sanity API usage so high on a small site?

Usually the site is querying Sanity on every request instead of caching, or bots and previews are hitting uncached pages. Image URLs requested at full size and queries that bypass the CDN also add up.

Should I use the Sanity CDN for my Next.js site?

For published content, yes: set useCdn to true and let Next.js cache and revalidate by tag. Keep the non-CDN API for draft mode and server-side reads that must be instantly fresh.

Does upgrading my Sanity plan fix a usage spike?

It raises your limits but does not remove the cause, so the spike usually returns at the next traffic jump. Fix caching and query habits first, then size the plan to real traffic.