Use Sanity with Next.js

Caching and revalidation in Next.js

Manual caching strategies for Next.js + Sanity apps. Covers the sanityFetch helper, time-based, tag-based, and path-based revalidation, and debugging.

If you're using defineLive from next-sanity/live, you probably don't need this article. defineLive handles caching, revalidation, and real-time updates automatically. It's the recommended approach for most applications.

This article is for apps that need fine-grained control over caching, are building mostly-static sites, or don't use the Live Content API. It covers how to implement manual caching strategies with Sanity and Next.js.

Two functions named sanityFetch

Two caching layers

When your content lives in Sanity and your frontend runs on Next.js, there are two independent caching layers to reason about:

Layer 1: the Sanity CDN. When your client is configured with useCdn: true, API responses are cached at the edge. This is fast but introduces a short delay before new content is available. You control this with the useCdn option on your Sanity client configuration.

Layer 2: the Next.js data cache. Next.js caches the results of fetch calls on the server. You control this with revalidate (time-based) and tags (on-demand) options passed to fetch. This cache persists across requests and, on some hosts like Vercel, survives redeployments.

These layers are independent. A page can show stale content because of either cache, or both. Understanding this prevents debugging headaches.

For a deeper look at how Next.js caching works, see the Next.js caching documentation.

Choose your strategy

StrategyFreshnessComplexityBest for
Time-basedDelayed (you set the interval)LowContent that changes infrequently
Tag-basedOn-demand (webhook/function-triggered)MediumChanges that affect many pages (authors, categories)
Path-basedOn-demand (webhook/function-triggered)MediumKnown URL-to-document mappings

Tags and time-based revalidation are mutually exclusive

The sanityFetch helper

This wrapper around client.fetch sets Next.js caching options for every query:

How the helper works:

  • Default behavior: every query is cached for 60 seconds (revalidate: 60), then Next.js fetches fresh data on the next request.
  • With tags: time-based revalidation is disabled (revalidate is set to false). The cache lives indefinitely until you call revalidateTag() from a webhook handler.
  • With revalidate: false: the cache lives indefinitely. Use this with tag-based or path-based revalidation. This is the preferred setting for static sites that want to handle revalidation manually.

Time-based revalidation

Time-based revalidation is the simplest strategy. Set a revalidate interval and Next.js handles the rest.

Guidelines for choosing a revalidate value:

ValueBehaviorUse when
30–60Revalidate every 30–60 secondsContent changes frequently (news, live scores)
3600Revalidate once per hourContent changes a few times per day
86400Revalidate once per dayContent rarely changes (about pages, legal text)
falseNever revalidate automaticallyYou're using tag-based or path-based revalidation

Time-based revalidation works well for most applications. If you need faster updates for specific content, combine it with path-based revalidation for those routes.

Tag-based revalidation

Tag-based revalidation gives you fine-grained control. Instead of revalidating on a timer, you tag queries and invalidate specific tags when content changes.

Tagging queries

Pass a tags array to sanityFetch:

const posts = await sanityFetch({
  query: POSTS_QUERY,
  tags: ['post'],
})

const authors = await sanityFetch({
  query: AUTHORS_QUERY,
  tags: ['author'],
})

// This query depends on both types
const postsWithAuthors = await sanityFetch({
  query: POSTS_WITH_AUTHORS_QUERY,
  tags: ['post', 'author'],
})

When you tag a query, Next.js associates the cached response with those tags. Calling revalidateTag('post') invalidates all cached queries tagged with 'post', including postsWithAuthors above.

Busting tags with webhooks

To trigger revalidateTag() when content changes in Sanity, set up a webhook handler or Sanity Function. See Validating Sanity webhooks in Next.js for the complete implementation.

The short version: create an API route that receives webhook payloads from Sanity, validates the signature, and calls revalidateTag(body._type).

Path-based revalidation

Path-based revalidation lets you revalidate specific routes by URL path. Use it when you have a clear mapping between documents and routes.

When a Sanity document changes, a webhook or function sends the affected path to your Next.js API route, which calls revalidatePath():

// In your webhook handler:
revalidatePath('/posts/my-post')

This evicts the cached page at /posts/my-post. The next visitor gets a freshly rendered page.

To revalidate all routes at once:

revalidatePath('/', 'layout')

This is a blunt instrument but useful as a fallback or for global content changes (site settings, navigation).

See Validating Sanity webhooks in Next.js for the complete API route implementation, including GROQ projections that dynamically generate paths from document data.

Debugging

When cached content isn't updating as expected, enable fetch logging in your Next.js configuration:

This logs every fetch call with the full URL, whether the response was a cache HIT or MISS, and the revalidate and tags values applied. Look for:

  • Unexpected HITs: the cache isn't being invalidated when you expect. Check that your webhook is firing and that tags match.
  • All MISSes: nothing is being cached. Check that revalidate isn't set to 0 and that you're not accidentally passing conflicting cache options.
  • Stale data after webhook fires: the Sanity CDN may still be serving old data. Pass true as the third argument to parseBody in your webhook handler to add a propagation delay. See Validating Sanity webhooks in Next.js for details.

Comparison with defineLive

Manual caching (this article)defineLive
FreshnessConfigurable (seconds to indefinite)Real-time
SetupsanityFetch helper + webhook/function handlersdefineLive + SanityLive component
ComplexityMedium (you manage caching strategy)Low (automatic)
Visual EditingRequires separate setupBuilt-in support
Best forStatic sites, fine-grained controlMost applications

If you started with manual caching and want to upgrade to real-time updates, see the Live Content guide.

Related resources

Was this page helpful?