Optimizing Jamstack Build Times with Incremental Static Regeneration (ISR)
Learn how to solve the scaling bottleneck of static site generation by implementing Incremental Static Regeneration (ISR) to update content without full site rebuilds.
04 Jun 2026, 02:03 UTC

The primary bottleneck in a pure Static Site Generation (SSG) workflow is build time. Pre-rendering pages into HTML offers strong performance and a small attack surface, but sites with thousands of pages can suffer CI/CD pipelines that take many minutes to complete. If every typo fix requires a full-site rebuild, the publishing loop becomes inefficient.
Incremental Static Regeneration (ISR) solves this by letting you update specific static pages after the initial build has finished. Instead of a global rebuild for every content change, the framework regenerates individual pages in the background as requests arrive.
Prerequisites for ISR
To implement ISR effectively, your architecture must include:
- A framework that supports ISR (for example, Next.js; the examples below assume the Pages Router convention).
- A deployment platform that can run regeneration logic at the edge or via serverless functions (for example, Vercel, Netlify, or a self-hosted Node.js server).
- A headless CMS or database acting as the source of truth for content.
Implementing the Revalidation Strategy
In a standard SSG setup, you fetch all data at build time. To move to ISR, you define a time-to-live (TTL) for each page. When the TTL expires, the next request to that page triggers a background regeneration. Visitors continue to see the stale page until the new one is successfully generated and cached.
Below is an example configuration for a product page that revalidates at most once every 60 seconds. The fallback: 'blocking' setting means product pages not built at compile time are generated on the first request, so new products work without a rebuild.
// pages/products/[id].js
export async function getStaticPaths() {
// Pre-build only the most popular products at compile time
const res = await fetch('https://api.example.com/products');
const products = await res.json();
return {
paths: products.map((p) => ({ params: { id: p.id } })),
fallback: 'blocking', // Generate new product pages on demand
};
}
export async function getStaticProps({ params }) {
const res = await fetch(`https://api.example.com/products/${params.id}`);
const product = await res.json();
return {
props: { product },
revalidate: 60, // Regenerate at most once every 60 seconds
};
}
export default function Product({ product }) {
return (
<div>
<h1>{product.name}</h1>
<p>Price: ${product.price}</p>
</div>
);
}
Understanding the ISR Lifecycle
With revalidate: 60, requests follow this logic:
- First request: The user receives the static page from the CDN cache.
- Requests within 60 seconds: Users receive the cached version immediately.
- First request after expiry: That user still sees the stale page, but the platform triggers a background regeneration.
- Completion: Once regeneration succeeds, the cache updates and the next visitor sees fresh content.
Tuning the Revalidation Interval
The TTL is a trade-off between freshness and load. A 60-second interval suits pricing or inventory pages; a blog post might tolerate an hour or more. Setting it too low wastes compute by regenerating pages whose data rarely changes; setting it too high risks stale content, which is the most common ISR complaint. Match the interval to how frequently the underlying data actually changes, not to how fast you wish it felt.
Verification and Diagnostics
1. Confirm the build output
After your CI pipeline completes, inspect the build directory (for example, .next or the exported output folder) and confirm it contains fully rendered HTML and CSS for your pre-built paths. Pages using fallback: 'blocking' will not appear there; that is expected, not an error.
2. Test revalidation end to end
Update a field in your CMS, wait longer than the TTL, then request the page twice. The first request may still serve the stale version (it triggers regeneration); the second should show the updated content. If neither request reflects the change, check that your data fetch is not being cached separately by fetch or an HTTP cache layer in front of your API.
3. Inspect cache headers
In browser developer tools, open the Network tab and check the response headers for the page. Look for CDN cache indicators (such as a cache status header showing a HIT or STALE state) to confirm assets are served from the edge rather than the origin on every request.
Limitations and Recovery
- Stale reads are by design: The first visitor after expiry always gets the old page. If you need guaranteed freshness (for example, a regulatory price change), use on-demand revalidation triggered by a CMS webhook instead of a time-based TTL.
- Failed regeneration keeps the old page: If your API is down when regeneration runs, the platform retains the last good version. This is a resilience benefit, but monitor regeneration errors so silent failures do not mask a broken data source.
- Client-side fetching still matters: Highly dynamic fragments (cart counts, personalized greetings) should load client-side against serverless functions. Implement skeleton placeholders to avoid layout shift while those requests resolve.
Because ISR is a configuration change rather than a data migration, rollback is straightforward: remove the revalidate key and redeploy to return to pure build-time SSG.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.