Skip to content
Docs

How To Reduce ISR Revalidation Costs

Reduce ISR costs by analyzing Incremental Static Regeneration (ISR) behavior to find pages and tags that revalidate too often

9 min read
Last updated July 9, 2026

Incremental Static Regeneration (ISR) serves a cached page instantly and regenerates it in the background, so users get a fast response while your content stays current. This guide shows you how to read Vercel's caching observability to find pages and cache tags that revalidate more than their content changes, and how to reduce revalidations and associated costs.

This guide assumes you already use ISR. For setup and framework configuration, see Incremental Static Regeneration.

In this guide, you'll learn how to:

  • Understand what actually drives ISR cost.
  • Analyze revalidation volume with Vercel's ISR observability.
  • Choose the right revalidation strategy to optimize page freshness and costs.

Every ISR request is served from one of three places:

  • CDN Cache, the cache in every Vercel region to serve cached content closest to the user.
  • ISR Cache, a durable single-region store that holds ISR page content for 31 days or until you revalidate them.
  • Origin, where your function runs, renders the page, and writes the result back to the cache. When the origin is hit, a function needs to be invoked and the content has to be written to the cache, which is slow and will incur additional costs.

A revalidation or purge causes the cache entry to become stale or deleted, and it will cause your function to be re-run to regenerate the page. ISR costs are incurred when the ISR cache is read or written to, so optimizing cache invalidations is critical to reduce costs.

Every ISR response carries a cache state in the x-vercel-cache header, and Logs surface the same value. See Cache Status and Reasons for the full list of states and cache reason values.

Revalidations are the primary driver of ISR cost. Every revalidation re-runs your function to regenerate the page and writes the new result to the ISR cache. Since the cache is per-region, the next request in each region pays a fresh read from the ISR cache into that region's CDN Cache. Frequent revalidation of rarely-changing content is the most common efficiency problem. For a full explanation of ISR costs, see ISR usage and pricing.

When the underlying content hasn't changed, ISR doesn't charge you for regenerating the same output. You do still pay for the extra CDN reads that follow, because a revalidation clears the page from every regional CDN Cache, and each region has to pull the content back from the ISR cache the next time a user requests it. That's why revalidating a page more often than its content changes wastes reads even when the regenerated output is identical.

When many requests reach the same uncached path at once, Vercel collapses them into a single function invocation per region. The first request runs your function; the rest wait for that result instead of each triggering their own invocation. This protects your origin from a cache stampede during traffic spikes, and it also applies when serving STALE responses, so concurrent background revalidations collapse into one invocation. See request collapsing for more details.

Vercel labels collapsed requests in the cache reason field (covered in Find the reason in Logs), so you can see when a slow request waited on a collapsed leader rather than running its own invocation.

ISR gives you two ways to decide when a cached page becomes stale, and the choice directly shapes how many revalidations you pay for.

  • Time-based revalidation fires on a fixed schedule. You set an interval (for example, revalidate: 60), and the cache entry is marked stale after that interval whether or not the underlying data changed. This method is predictable and easy, but it regenerates content whether or not the content has changed.
  • Tag-based revalidation fires only when your code signals that data changed. You attach one or more tags to a cache entry, then call revalidateTag (or revalidatePath) from a Server Action or Route Handler after the change. Regenerations happen only when the content has changed.

Time-based revalidation is the right default when you don't have a hook into the data change (for example, content sourced from a third-party feed you don't control). Tag-based revalidation is the largest efficiency win for projects with event-driven data, because it eliminates regenerations that would produce the same output. Most CMS providers offer webhooks that can be used to revalidate content only when the CMS entry has changed.

Many projects combine both, using a long time-based interval as a safety net and tag-based revalidation for the specific events that matter.

Before tuning ISR, use observability to see where revalidations are actually happening and whether they're justified by the change rate of the underlying content.

Use the ISR observability page to find content that regenerates more often than it needs to. Open it from the dashboard under Observability, then the ISR tab: ISR observability.

Look at the overall number of time-based revalidations across your routes in the observability table. Sort by revalidations in the data table to see which routes generate the most volume, and treat the top of that list as your first tuning target.

From there, you'll likely want to choose a higher revalidation period for routes that are over-revalidating relative to how often their data changes. Once you've pushed the interval as far as freshness allows, consider switching to tag-based revalidation so regenerations happen on the events that actually change the data rather than on a timer.

Use the tag drill-down in the table to see which cache tags drive revalidations, and watch for two patterns that indicate waste:

  • High frequency: A tag fires revalidateTag more often than its underlying data changes.
  • Large blast radius: One tag covers many ISR entries, so a single revalidation of that tag regenerates a large number of pages at once.

If a tag has a wide blast radius, narrow it so a change regenerates only the affected pages.

Select a route to see the revalidations specific to that route, broken down by path and tag. This isolates which exact paths or tags under a route drive your revalidation volume, one level deeper than the aggregate view.

Logs show why an individual request was a MISS, STALE, BYPASS, or REVALIDATED through a cache reason field. Use it to confirm a hypothesis from the Efficiency view. Reasons include:

  • cold (MISS): The content wasn't in the cache (never generated, or evicted), so the request went to the origin.
  • collapsed (MISS): The request waited on a collapsed leader rather than running its own invocation.
  • error (MISS): the cache lookup failed and the request fell back to the origin.
  • time-based revalidation (STALE): The page went stale because its interval elapsed, not because the data changed. Seeing this on a low-Utilization page confirms it revalidates on a timer you can lengthen, or move to tag-based revalidation.
  • tag-based invalidation (STALE): The page went stale because revalidateTag or revalidatePath fired. If you see this more often than the underlying data changes, inspect the tag's frequency and blast radius.
  • revalidation error (STALE): Vercel served the stale copy because a background revalidation failed. This is a health signal rather than a cost signal, but repeated errors mean the page keeps attempting to regenerate.
  • draft mode, prerender bypass, crawler (BYPASS): The response was intentionally not cached, so it doesn't count toward ISR revalidation. However, this will cause a function invocation to occur.
  • tag-based deletion (REVALIDATED): A purge or delete-by-tag removed the entry, so the next request regenerated the page. Like tag invalidation, check whether the delete fires more often than the content requires or switch to use revalidation instead of a purge so that the next request serves stale content while the page is regenerated.

For the full list of cache reason values, see Cache Status and Reasons.

Read the cache reason from the CLI, either for a single request or across all your traffic:

# Inspect the cache reason for one request
vc logs --request-id <request-id> --expand --json --project <project> --scope <team>
# Count how often each reason occurs over the last 24 hours
vc metrics vercel.request.count --group-by cache_reason --since <time> --project <project> --scope <team>

Group vc metrics by cache_reason to see which reasons dominate your revalidations, then use vc logs on a sample request to inspect the ones behind a spike.

Once you've identified low-Utilization pages and hot tags, the fix falls into one of two categories. Either the page is revalidating on a timer that's shorter than it needs to be, or it should move off a timer entirely and revalidate on the events that actually change its data.

Time-based revalidation with revalidate: N fires on a fixed schedule regardless of whether content changed. The shorter the interval, the more you pay for pages that produce the same output. Lengthen the interval whenever freshness isn't critical. A route that revalidates every 60 seconds costs 60× more regenerations than one that revalidates every hour, even if the underlying data changes once a day. Pick the longest interval that still meets your freshness requirement, and keep short intervals only when you need the freshest possible content and accept the cost.

In the classic ISR model, you set the interval on the route or the fetch:

  • revalidate: N, set on a route or fetch, regenerates on the interval whether or not anything changed.

For the newer Cache Components model, cacheLife controls how long a cached value stays fresh. Cache Components are a Next.js caching model built around the "use cache" directive. Cache Components are enabled in your Next.js config:

next.config.ts
const nextConfig: NextConfig = {
cacheComponents: true,
}

The "use cache" directive caches the return value of an async function or component and applies at the file, component, or function level. Inside a "use cache" scope, cacheLife(profile) sets how long the cached value stays fresh, when background revalidation triggers, and the hard expiry. Built-in profiles include 'hours', 'days', and 'weeks'. You can define custom profiles in next.config.ts. Picking a longer profile is the Cache Components equivalent of lengthening revalidate: N.

import { cacheLife } from 'next/cache'
async function getPosts() {
'use cache'
cacheLife('hours')
return fetch('https://api.example.com/posts').then(r => r.json())
}

Tag-based revalidation only fires when your code signals that data changed, so pages regenerate on the event that made the old content wrong instead of on a timer. This is the largest efficiency win for ISR projects with event-driven data.

In the classic ISR model, call revalidateTag or revalidatePath from a Server Action or Route Handler after a data change. Only the tagged or pathed entries regenerate.

For the Cache Components model, tag a cache entry with cacheTag, then call revalidateTag(tag, 'max') to invalidate it on demand.

import { cacheLife, cacheTag } from 'next/cache'
async function getPosts() {
'use cache'
cacheLife('hours')
cacheTag('posts')
return fetch('https://api.example.com/posts').then(r => r.json())
}
'use server'
import { revalidateTag } from 'next/cache'
export async function onPostUpdated() {
revalidateTag('posts', 'max')
}

revalidateTag with 'max' marks the tagged data as stale and triggers background revalidation with stale-while-revalidate semantics, so users continue to get the cached value while the next read refreshes it. Scope on-demand revalidation to the tags that changed rather than issuing a broad purge, which minimizes the number of pages that regenerate per event.

Follow these steps to find and fix inefficient revalidation:

  1. Question the revalidation rate: For the worst offenders, ask whether the content changes that often.
  2. Switch or lengthen: If the content doesn’t change that often, lengthen revalidate or move to revalidateTag or revalidatePath.
  3. Check hot tags: If a tag revalidates frequently, inspect its blast radius and narrow it.
  4. Drill down: Open the route to confirm which paths drive the volume.
  5. Confirm in Logs: Check the cache reason for a sample of requests (for example, Cold, Request Collapsed, or Time-based revalidation). Read it in the Logs tab, or from the CLI with vc logs --request-id <request-id> for a single request or vc metrics vercel.request.count --group-by cache_reason to count reasons across your traffic.
  6. Use scoped tag invalidation: Keep regenerations in the background by targeting only the tags that changed, not the whole project.
  7. Monitor over time: After deploying, re-check Utilization over the next 24 hours to confirm the trend holds.

Was this helpful?

supported.