Stale Sanity Content in Next.js: The Two Caches and How to Fix Them
Sanity content not updating in Next.js, or only showing after a redeploy? Two caches cause it: the Sanity CDN and the Next.js Data Cache. How each one works and how to fix stale content.
- [Author]
- Edoardo Lunardi
- [Published]
- [Reading time]
Two caches, not one
- •The Sanity CDN, controlled by the
useCdnflag on your client. With it on, Sanity serves a cached response from the edge: fast, and a few seconds to a few minutes behind the latest publish. - •The Next.js Data Cache, controlled by the
revalidateandtagsoptions you pass to a fetch. It persists across requests and, on Vercel, survives redeploys.
Find which cache is serving the old copy
logging.fetches.fullUrl to true in next.config.ts and every server fetch prints to the terminal with a cache HIT or MISS and the tags applied. A HIT where you expected fresh data means the Next.js Data Cache is holding the old copy and your revalidation never ran. A MISS that still returns the old value means the request read Sanity through the CDN and got a stale edge copy, the case bypassing the CDN in production removes. One line of config turns a guessing game into a reading.The Sanity CDN serves the first stale copy
useCdn: true and Sanity returns content from its edge cache. For a marketing page that updates a few times a week, that delay is invisible and the bandwidth savings are real. The trouble starts when you wire Next.js revalidation on top of it. Next.js revalidates, calls Sanity for fresh data, and Sanity hands back the same cached response it had a moment ago. Your revalidation worked. It refilled the Next.js cache with stale data from the CDN.useCdn: false so a production read hits Sanity's live API and returns the current content, with no edge copy to fall behind. The cost worry, that the live API allowance is smaller and pricier than the CDN's, does not bite: in production the Next.js Data Cache fronts every fetch, with force-cache and tags, so a page serves from cache until a webhook busts its tag. The live API call fires only on the first request after an invalidation, so a busy site makes a handful of Sanity calls a day, not one per visitor. You touch the API only when content actually changed.useCdn goes true and a few seconds of edge lag costs nothing while you build. Bypassing the CDN in production also closes a gap that catches teams who leave it on: a webhook fires before the edge finishes propagating, so a revalidation that reads the CDN re-caches the old value. parseBody from next-sanity takes a delay argument to cover that window, but with the CDN out of the production path the window never opens.Time-based revalidation is a guess
revalidate: 60 tells Next.js to serve a cached page for up to sixty seconds before regenerating it. For content that changes on a schedule, that is fine. For a typo fix an editor wants live now, it means up to sixty seconds of the wrong text on a page someone is reading. Shorten the window and you trade freshness for load on Sanity. Set revalidate: 0 and you remove caching entirely, which solves staleness by giving up the performance you came to Next.js for.revalidate: 0 means revalidate on every request, so nothing is cached. revalidate: false means cache until something invalidates it by hand. They read as similar and behave as opposites. Pick false when you intend to control freshness yourself, which is where tags come in.Tag-based revalidation is the production answer
homepage and a post tagged post:${slug} revalidate independently, each one only when its own document moves.sanityFetch helper passes tags and, when tags are present, sets revalidate: false so the two strategies never fight. A route handler at /api/revalidate in the App Router validates the request and calls revalidateTag for whatever changed. A GROQ-powered webhook in Sanity calls that route on create, update, and delete, with a filter narrow enough that you are not revalidating the world on every keystroke.// Invalidation flow
//
// Publish -> GROQ webhook -> POST /api/revalidate -> revalidateTag(tag)
// -> Next.js Data Cache drops the tag -> next request MISS
// -> Sanity live API (useCdn: false) -> fresh page
Build notes from The Content Architecture
The Content Architecture is a Next.js and Sanity starter built on decisions like this one. The list covers the parts that take the longest to get right: the fetch layer, the agentic layer, the parts that never make it into an estimate. New breakdowns land here first. Low volume, high signal.
Draft mode is the same problem inverted
published to drafts and forcing useCdn: false, so the Studio preview shows work in progress instead of the live page. It is the two-cache model again, read from the other side. Get the boundary right once and preview, live content, and revalidation all flow through the same sanityFetch, each picking the right cache for its job.Why not let defineLive do all of this
defineLive, a fetch helper and a <SanityLive> component that handle caching, revalidation, and draft mode for you, with real-time updates as content changes. For most applications it is the recommended path, and it removes most of the wiring above. I still reach for the manual setup on production builds, for one current reason.<SanityLive> interacts with the default link prefetch in a way that multiplies requests: a published change invalidates the client cache, prefetches fire again, tagged routes re-fetch and re-write, and your Sanity API and Vercel ISR bills climb with traffic. Sanity has documented this and for now recommends Next.js 15 with the older toolkit, or, on 16, driving revalidation from a Sanity Function instead of rendering <SanityLive> everywhere. Until it settles, a hand-built tag-based layer with the CDN bypassed in production is the cost I can predict.The part nobody quotes for
useCdn handled per environment, tag-based revalidation wired to a webhook, draft mode and live preview connected, so a client build starts past the part that usually costs the first three days. For the reasoning behind the rest of the system, the companion piece on content architecture covers how the schema underneath it is modeled.Common questions
Why is my Sanity content not updating in Next.js?
Why do my Sanity changes only show after a redeploy?
Should useCdn be true or false in production?
How do I revalidate a Next.js page when I publish in Sanity?
revalidateTag for the document type or slug that changed.Is defineLive safe to use on Next.js 16?
<SanityLive> component can multiply requests through link prefetch and raise your Sanity API and Vercel ISR bills. Sanity suggests staying on Next.js 15 with the older toolkit, or driving revalidation from a Sanity Function on 16. A manual tag-based layer with the CDN bypassed in production is the predictable option today.For engineers building on the stack
The Content Architecture is the Next.js and Sanity foundation with the fetch layer already decided. If you build on this stack, the list is where new patterns, deep dives, and product updates land first. No filler, just the engineering.