Help articles

Next.js 16 and SanityLive: avoiding request overages

Upgrading to Next.js 16 with SanityLive can multiply prefetch requests and ISR writes, leading to Sanity API overages. Workarounds and what to expect.

If you're using SanityLive in a Next.js app, upgrading to Next.js 16 can cause a large increase in requests and ISR writes compared to Next.js 15. That increase can drive up Sanity API usage and Vercel ISR costs. This article covers what to expect and how to work around it while a fix is in progress.

This applies if: you're running a Next.js App Router app, using next-sanity's defineLive and <SanityLive>, and you're upgrading from Next.js 15 to 16 (or have already upgraded).

Stay on Next.js 15 until next-sanity v12 ships

What to expect

With Next.js 16 and <SanityLive>, the default <Link> prefetch behavior combined with how <SanityLive> calls revalidateTag produces a cascade: each prefetch fires more requests than on v15, a live event invalidates the client-side cache, prefetches run again, and routes that match the revalidated tag re-fetch and write to ISR.

In production we've seen an average 4x increase in request load from the same app after upgrading. Worst cases (marketing and docs sites with many segments) are 7–10x or more.

How to tell if this is affecting you

  • Your app is on Next.js 16 with <SanityLive> in use.
  • Sanity API request counts jumped 4x or more after the upgrade, with no comparable change in traffic.
  • Vercel ISR writes spiked on routes that consume Sanity content.
  • Browser devtools show multiple ?rsc requests fired for each <Link> prefetch.

Safest way to run SanityLive on Next.js 16

If downgrading to Next.js 15 isn't an option and you're not using cache components, this is the safest setup today:

  • Upgrade to at least Next.js 16.2 and enable experimental.prefetchInlining.
  • Keep using sanityFetch from defineLive in production, but don't render <SanityLive> unless Presentation Tool or visual editing is active.
  • Use a sync tag function and set up an /api/expire-tags handler in your Next.js app that calls revalidateTag(tag, "max"). Then call this route from the sync tag function.
  • When you do render <SanityLive> (per step 2, for Presentation Tool or visual editing), render it only in draft mode and override revalidateSyncTags so it no longer calls revalidateTag(tag, { expire: 0 }).

The refreshAction implementation must live in a file marked 'use client' (not 'use server') and return 'refresh'. That tells <SanityLive> to call router.refresh() for you, which triggers a single GET that live-updates the page.

If you need cache components enabled

This combination is not supported on next-sanity@latest. Don't use defineLive on Next.js 16 with cache components. A manual setup is possible, but it does not support Presentation Tool's content releases perspective switching (previewing scheduled releases alongside published content in the live preview).

For draft-mode previewing, pass includeDrafts to the live events stream, call router.refresh() from draft mode, and toggle the fetch perspective based on draft state. The snippets below illustrate the pattern across multiple files in your app:

// Subscribe to draft events when draft mode is active
client.live.events({ includeDrafts: true })

// In your data fetcher
client.fetch(query, params, {
  perspective: isDraftMode ? 'drafts' : 'published',
})

// When an event arrives in draft mode
router.refresh()

Why Next.js 16 without cache components is risky

This combination is supported on next-sanity@latest, but it's considered dangerous and leads to overage risk. The default prefetch behavior on <Link> tags changed in v16 (see Next.js's "Incremental prefetching"). Combined with <SanityLive> calling revalidateTag(tag, { expire: 0 }), the result is:

  • Each <Link> prefetch fires many more requests than in v15. On sanity.io we observed 2 requests with ?rsc on v15 versus 7 on v16.
  • <SanityLive> receives a live event and calls revalidateTag(tag, { expire: 0 }). That server action nukes the Next.js client-side cache (router.refresh() has the same effect).
  • Clearing the client cache empties the prefetch cache. Next.js sees the visible Link tags and prefetches them all again.
  • If any of those prefetches hit a route tagged with the revalidated tag, Next.js responds CACHE: REVALIDATED (its internal signal that a tagged route was re-rendered), which performs a data fetch (counts toward Sanity API usage) and triggers an ISR write (billable on Vercel).

Next.js's core team has confirmed the prefetch request change. The CACHE: REVALIDATED behavior on prefetch links is still under investigation. We know it happens, but not yet why v16 behaves differently from v15.

Where the fix is tracked

Updates are landing in next-sanity v12 under the next-sanity@cache-components tag. Watch the next-sanity GitHub releases for the public v12 announcement.

Related articles

Was this page helpful?