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
We recommend staying on Next.js 15 with next-sanity v11. Don't upgrade to Next.js 16 until next-sanity v12 is released. v12 is currently being tested under the next-sanity@cache-components tag and is running in production on sanity.io (with cache components enabled) and sanity.io/docs (on v16 with cache components disabled). Status as of April 2026.
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
?rscrequests 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
sanityFetchfromdefineLivein 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-tagshandler in your Next.js app that callsrevalidateTag(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 overriderevalidateSyncTagsso it no longer callsrevalidateTag(tag, { expire: 0 }).
<>
{isDraftMode && <SanityLive revalidateSyncTags={refreshAction} />}
</>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.
'use client'
export async function refreshAction(): Promise<'refresh'> {
return 'refresh'
}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?rscon v15 versus 7 on v16. <SanityLive>receives a live event and callsrevalidateTag(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.