Keep Jamstack Fast Without Stale Content: Incremental Static Regeneration in Practice
Use Incremental Static Regeneration to serve pre-rendered Jamstack pages fast and refresh them in the background without full rebuilds.
11 Sept 2025, 02:23 UTC

A Jamstack site with thousands of product pages can build in minutes. Every content edit then forces a full rebuild, or visitors see stale data until the next deploy. Incremental Static Regeneration, ISR, addresses that tension by shipping pre-rendered HTML at build time and refreshing individual pages in the background after a set interval.
The useful takeaway: you keep the speed and SEO benefits of static HTML while allowing pages to update without rebuilding the whole site.
Why full static builds break down at scale
Jamstack pre-renders pages to static files and serves them from a CDN. That is fast and cheap. The cost is freshness. With a purely static workflow, new content requires a new build and a new deploy for all pages. For large catalogs or editorial sites, build time grows with page count and the deploy window becomes a bottleneck.
Server-side rendering fixes freshness but gives up the instant global edge cache of static files. ISR is a middle path: the first request after a deploy serves the last generated HTML, and a background revalidation job creates a new version for a later visitor.
How ISR works in the Next.js Pages Router
In the Next.js Pages Router, ISR is enabled by returning a revalidate value from getStaticProps. The value is in seconds. Next.js serves the cached HTML immediately and, on the first request after the interval expires, triggers a background regeneration of that page.
The framework emits cache-control headers that implement stale-while-revalidate semantics. The visitor always gets HTML, never a loading spinner, and a later visitor gets the refreshed version.
Important constraint: ISR requires a hosting platform that can run serverless functions or a Node.js server for the background job, such as Vercel or Netlify with Edge Functions. Purely static hosts like GitHub Pages cannot run revalidation.
Worked example: a product list page with time-based revalidation
Create a page under the Pages Router that fetches data at build time and opts into background refresh.
// pages/products.js
export async function getStaticProps() {
const res = await fetch('https://api.example.com/products');
const products = await res.json();
return {
props: { products },
revalidate: 60
};
}
export default function Products({ products }) {
return (
<main>
<h1>Products</h1>
<ul>
{products.map(p => <li key={p.id}>{p.name}</li>)}
</ul>
</main>
);
}Where to run: create the file locally in a Next.js project and deploy to a platform that supports ISR. Required permissions are read access to the data source and deploy permissions for the hosting platform.
Expected checks after deploy: request the page and confirm it returns HTML immediately. Update the source JSON endpoint, then request the page again after the revalidate window. The page should eventually reflect the new data without a new build.
Practical verification: inspect response headers for cache-control values that indicate stale-while-revalidate behavior. A common pattern is stale-while-revalidate=60 alongside an age header that grows with time. Exact header values depend on the framework version and host, so treat them as something to confirm rather than assume.
Risks: setting revalidate too low increases function invocations and cost. Setting it too high defeats timely updates. Dynamic routes also need a strategy for on-demand invalidation, since time-based revalidation alone may not be sufficient for urgent edits.
Trade-offs and limits to plan for
Users may see slightly outdated content for up to the revalidate interval. That window is the price for instant global delivery.
ISR adds reliance on serverless execution for background jobs. If the regeneration function fails, the previous static version continues to be served, which is safe but can mask data errors.
Cache invalidation logic must be explicit. For a path like /products/[id], each page has its own revalidate timer. You need to decide per route whether time-based refresh is enough or whether you need an on-demand purge.
Start with a conservative interval for high-traffic pages, monitor invocation counts, and tighten the interval only where freshness matters more than cost.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.