Type something to search...
Sanity CDN vs Live API: When to Use Each

Sanity CDN vs Live API: When to Use Each

Use the Sanity API CDN (useCdn: true, served from apicdn.sanity.io) for read queries that serve visitors: browser fetches, high-traffic server rendering and anything where a short delay before new content appears is acceptable. Use the live API (useCdn: false, served from api.sanity.io) when you need the latest saved content: static builds, webhook-triggered revalidation, draft previews, scripts that read and then write, and every mutation. The CDN is faster, cheaper and scales without rate limits on cache hits; the live API is always fresh but slower and more tightly limited.

The choice is one boolean in your client config, but it affects freshness, cost, rate limits and even whether your preview shows drafts. Getting it wrong usually shows up as "I published but the site didn't update" or a surprise overage on API requests. This article explains what each endpoint does, how caching works on the CDN, a decision table for common scenarios, how tokens and perspectives interact with useCdn, how this differs from the Live Content API, and how to configure clients so each request uses the right endpoint.

Two Endpoints, One API

Every Sanity project exposes the same query API on two hosts:

Live APIAPI CDN
Host<projectId>.api.sanity.io<projectId>.apicdn.sanity.io
Client settinguseCdn: falseuseCdn: true
FreshnessLatest saved content, every timeCached; new content appears after a short delay
SpeedEvery request reaches the Content LakeCache hits are served from the edge
Rate limitsConcurrency and per-IP limits applyCache hits aren't rate limited
SupportsQueries, mutations, listeners, everythingGROQ queries and GraphQL only
QuotaCounts as API requestsCounts as API CDN requests

The query syntax, response format and API versions are identical. Only the host changes. That's why most clients reduce the whole decision to a useCdn flag.

A note on naming: Sanity's docs call api.sanity.io the live, uncached API. That's different from the Live Content API, which is a real-time event stream your front end subscribes to. We'll come back to how they relate.

How the API CDN Caches

The API CDN is built for end-user read traffic, and its cache rules reflect that:

  • Cached: GET and HEAD requests, plus POST requests to /data/query and /graphql, because those endpoints are read-only. POST bodies are limited to 300 KB.
  • Not cached: non-200 responses and responses larger than 10 MB.
  • Rejected: any other POST, since it could contain a mutation. Send writes to the live API.
  • Authenticated requests are cached per token: a request with a bearer token gets its own cache segment, so different tokens never share results. Cookies are ignored.
  • Listeners are redirected: real-time listeners and the Live Content API always go to the live API.
  • Resilience: if the Content Lake is unavailable, the CDN can keep returning the last cached content for up to two hours.

Caching is keyed on the full request URL, including query parameters. Two requests with a different query string, parameter value or API version are two cache entries. Queries built with stable text and GROQ parameters therefore cache far better than queries with values interpolated into them, which is one more reason to follow the GROQ parameters guide.

When content changes, cached responses are updated after a short propagation delay rather than instantly. Sanity doesn't publish a fixed number for this, so design for "a few moments" of staleness, not zero.

Which One Should You Use?

ScenariouseCdnReason
Browser fetches (client components, SPAs)trueFast, scales with traffic, no token needed for public data
Server rendering for high-traffic pagestrueAvoids live API rate limits under load
Static generation and generateStaticParamsfalseA build should capture the latest content
Revalidation triggered by a webhookfalseThe page must rebuild from the content that triggered it
Draft Mode, previews, Visual EditingfalseDrafts need a token and the latest edits
Scripts that read, then patchfalseNever base a write on cached data
MutationsNot applicableAlways sent to the live API
Sitemaps, feeds, search indexing jobsfalse (or true with a short cache)Usually run rarely and need recent content

When in doubt, Sanity's own guidance is to default to useCdn: true and switch specific requests to false.

Why Static Builds Should Skip the CDN

A build runs once and the result is served for hours or days. If it reads from a cache that's a few moments behind, the page you just published might be missing from a build that started because you published it. The live API avoids that. The trade-off is rate limits: a large static site that fetches thousands of pages in parallel can hit the live API's concurrency limit, so cap parallel requests during builds. The static site with Astro and Sanity post shows a build-time setup.

Why Webhook Revalidation Should Skip the CDN

A webhook fires when content changes. If your handler revalidates a page and the page immediately refetches from the CDN, it can cache the old version again. Fetch with useCdn: false in revalidated code paths, or add a short delay. parseBody from next-sanity/webhook waits a few seconds by default for exactly this reason. The webhook rebuild guide covers the full flow.

Configure Clients for Both

Create one base client with the CDN enabled, then derive variants with withConfig():

// src/sanity/lib/client.ts
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-02-19',
  useCdn: true,
  perspective: 'published',
})

// Fresh reads for builds, revalidation and scripts
export const freshClient = client.withConfig({ useCdn: false })

withConfig() returns a new client and leaves the original untouched, so you can pick per call site:

// src/app/blog/[slug]/page.tsx
import { notFound } from 'next/navigation'
import { client, freshClient } from '@/sanity/lib/client'
import { defineQuery } from 'next-sanity'

const SLUGS_QUERY = defineQuery(`*[_type == "post" && defined(slug.current)].slug.current`)
const POST_QUERY = defineQuery(`*[_type == "post" && slug.current == $slug][0]{ title, body }`)

export async function generateStaticParams() {
  const slugs = await freshClient.fetch(SLUGS_QUERY)
  return slugs.map((slug) => ({ slug }))
}

export default async function PostPage({ params }: { params: Promise<{ slug: string }> }) {
  const { slug } = await params
  const post = await client.fetch(POST_QUERY, { slug })
  if (!post) notFound()
  return <h1>{post.title}</h1>
}

You can also override per request with the third argument to fetch():

const latest = await client.fetch(POST_QUERY, { slug }, { useCdn: false })

If you use sanityFetch from defineLive in next-sanity, it chooses for you: requests with the published perspective use the CDN, and other perspectives, such as drafts, bypass it. The next-sanity guide covers that helper in detail.

Tokens, Perspectives and the CDN

Older versions of @sanity/client silently skipped the CDN whenever a token was set. Current versions don't: authenticated requests go to the CDN when useCdn is true, with a cache segment per token. That's useful for private datasets, which can be served from the API CDN as long as each request carries a token, but it also means you should be deliberate:

  • Draft previews: use useCdn: false with the drafts perspective. Editors expect to see what they typed a second ago.
  • Private datasets on public pages: fetch on the server with a Viewer token and useCdn: true, and cache the rendered page. Never put the token in the browser.
  • One token per purpose: because the cache is segmented by token, sharing a token across unrelated apps doesn't improve hit rates and makes revocation harder.

Remember the default perspective. With apiVersion set to 2025-02-19 or later, queries return the published perspective unless you ask otherwise, so drafts never leak into CDN responses by accident. The Content Lake post explains perspectives.

Quotas and Rate Limits

The two endpoints are metered separately, and the CDN allowance is much larger. On the Free and Growth plans, the included API CDN request quota is four times the live API quota, as listed in the Sanity pricing breakdown. A site that accidentally sends all public traffic to the live API burns through its quota and, on Growth, pays overages.

The live API also enforces limits that cached CDN hits avoid:

  • Concurrency per dataset: 500 queries and 100 mutations in flight at once.
  • Per IP: 500 API calls, 25 mutations and 25 uploads per second.

Exceeding a limit returns HTTP 429. @sanity/client retries rate-limited queries with exponential backoff, but it doesn't retry mutations, so batch writes into transactions and limit their concurrency yourself.

Where the Live Content API Fits

The Live Content API solves a different problem: telling your front end that content has changed. Clients subscribe to sync tags, identifiers returned with query results, and receive an event when content behind those tags changes. Your app then refetches only the affected queries.

It complements the CDN rather than replacing it:

  1. Pages fetch with the published perspective through the CDN.
  2. A live connection listens for changes to the sync tags those queries returned.
  3. When an editor publishes, the event arrives, the app revalidates, and the refetch picks up the new content.

In Next.js, defineLive and the <SanityLive /> component wire this up and handle caching for you. The Live Content API itself always connects to the live API, never the CDN. For a full walkthrough, see Using Live Preview With Sanity, and for layering it with framework caches, Improving Site Performance With Sanity Caching.

Check Which Endpoint You're Hitting

When freshness looks wrong, confirm the host in the network tab or your server logs:

https://abc123.apicdn.sanity.io/v2025-02-19/data/query/production?query=...   # CDN
https://abc123.api.sanity.io/v2025-02-19/data/query/production?query=...      # live API

You can also test with curl, which is handy for comparing responses straight after publishing:

curl -s "https://abc123.api.sanity.io/v2025-02-19/data/query/production?query=*%5B_type%3D%3D%22post%22%5D%5B0%5D%7Btitle%7D"
curl -s "https://abc123.apicdn.sanity.io/v2025-02-19/data/query/production?query=*%5B_type%3D%3D%22post%22%5D%5B0%5D%7Btitle%7D"

If the live API shows the new title and the CDN doesn't, wait a moment. If both are stale, the problem is elsewhere: perhaps the document wasn't published, or your framework's own cache is serving an old page. The HTTP API post covers the raw endpoints.

Common Mistakes

  • useCdn: false everywhere "to be safe": it makes the site slower, uses the smaller quota and exposes you to rate limits under traffic.
  • useCdn: true in revalidation handlers: the rebuilt page can cache stale content again.
  • Previews through the CDN: editors see old drafts and lose trust in the preview.
  • Reading from the CDN before writing: patches based on cached data can overwrite newer edits. Use the live API and ifRevisionId for safe writes.
  • Unstable query URLs: interpolating timestamps or random values into queries defeats the cache. Use parameters with stable values.

FAQ: Sanity CDN vs Live API

It chooses the host. With useCdn true, read queries go to apicdn.sanity.io, the cached API CDN. With useCdn false, they go to api.sanity.io, the live, uncached API. Mutations and listeners always use the live API whatever the setting.

Briefly. Cached responses update a short time after content changes, but Sanity doesn't guarantee a fixed delay. If a request must reflect content saved a second ago, such as a preview or a webhook-triggered rebuild, use the live API.

Yes. Current clients send authenticated requests to the CDN when useCdn is true, and the cache is segmented per token. That makes the CDN suitable for private datasets queried from the server. Keep the token server-side.

No. The live API is the uncached query endpoint at api.sanity.io. The Live Content API is a real-time event stream that notifies your app when content behind its queries changes, so it knows when to refetch. They work together with the CDN rather than replacing it.

Generally no. A build should capture the latest published content, so use useCdn false. For very large sites, limit concurrent requests during the build so you stay under the live API's concurrency limit.

No. The API CDN caches query responses. Images and files are served from a separate asset CDN at cdn.sanity.io, which is always used for assets regardless of the useCdn setting.


Conclusion

The API CDN and the live API return the same data from the same query language. The difference is freshness versus scale. Route public, read-only traffic through the CDN with useCdn: true, and switch to useCdn: false for builds, revalidation, previews and any read that feeds a write. Configure one base client with the CDN on and derive a fresh variant with withConfig(), so each call site makes the right choice explicitly.

Add the Live Content API when you want pages to update as soon as editors publish, and keep query URLs stable so the cache can do its job. With those pieces in place, your site stays fast under traffic, stays inside its quotas and still shows new content quickly.

Tags :
Share :

Related Posts

Building a Static Site With Astro and Sanity

Building a Static Site With Astro and Sanity

To build a static site with Astro and Sanity, keep Astro's default output: 'static', configure @sanity/astro with useCdn: false, and fetch

Dive Deeper
Common Sanity Mistakes and How to Fix Them

Common Sanity Mistakes and How to Fix Them

The most common Sanity mistakes are using the CDN where you need fresh data, fetching whole documents with ..., leaving slugs unvalidated, ship

Dive Deeper
How to Create a Custom Sanity Plugin

How to Create a Custom Sanity Plugin

To create a custom Sanity plugin, wrap a piece of Studio configuration in definePlugin from the sanity package and add it to the plugins ar

Dive Deeper