Skip to content

Incremental Static Regeneration — Updating Static Content Without Full Rebuilds

DodaTech Updated 2026-06-28 5 min read

In this tutorial, you will learn about Incremental Static Regeneration. We cover key concepts, practical examples, and best practices to help you master this topic.

Incremental Static Regeneration (ISR) updates static pages after deployment by re-rendering individual pages in the background without rebuilding the entire site.

What You'll Learn

By the end of this tutorial, you'll understand what ISR is, how it extends SSG, when to use time-based revalidation, and how ISR balances freshness with performance.

Why It Matters

Traditional SSG requires a full rebuild to update any content. For large sites, rebuilding can take hours. ISR solves this by re-rendering only the changed pages in the background, keeping your site fast and fresh without full rebuilds.

Real-World Use

A blog with 5,000 articles publishes 10 new posts daily. Instead of rebuilding all 5,000 pages, ISR re-renders only the 10 new articles and any updated ones. Readers always see fresh content while the build pipeline stays fast.

ISR Flow

graph LR
    A[User Request] --> B{Page cached?}
    B -->|Yes, fresh| C[Serve cached
static HTML] B -->|Yes, stale| D[Serve stale
static HTML] B -->|No| E[Fallback render] D --> F[Background
revalidation] F --> G[New static
HTML generated] G --> H[Update cache] H --> I[Next request serves fresh] E --> J[Generate + cache] J --> C style D fill:#f39c12,color:#fff style F fill:#4a90d9,color:#fff style G fill:#27ae60,color:#fff

Time-Based Revalidation

// pages/posts/[slug].js — With ISR revalidation
export default function Post({ post, generatedAt }) {
    return (
        <article>
            <h1>{post.title}</h1>
            <p className="meta">
                Updated: {new Date(post.updatedAt).toLocaleDateString()}
                <br />
                Page generated: {new Date(generatedAt).toLocaleString()}
            </p>
            <div>{post.content}</div>
        </article>
    );
}

export async function getStaticPaths() {
    const res = await fetch('https://api.example.com/posts');
    const posts = await res.json();

    return {
        paths: posts.map(post => ({
            params: { slug: post.slug }
        })),
        fallback: 'blocking'
    };
}

export async function getStaticProps({ params }) {
    const res = await fetch(`https://api.example.com/posts/${params.slug}`);
    const post = await res.json();

    return {
        props: {
            post,
            generatedAt: Date.now()
        },
        // Revalidate every 60 seconds
        revalidate: 60
    };
}

On-Demand Revalidation

// pages/api/revalidate.js — Webhook endpoint
export default async function handler(req, res) {
    // Verify webhook secret
    if (req.query.secret !== process.env.REVALIDATION_TOKEN) {
        return res.status(401).json({ message: 'Invalid token' });
    }

    try {
        const { slug } = req.body;

        // Revalidate specific paths
        await res.revalidate(`/posts/${slug}`);
        await res.revalidate('/'); // Also revalidate homepage

        return res.json({ revalidated: true });
    } catch (err) {
        console.error('Revalidation failed:', err);
        return res.status(500).json({ message: 'Error revalidating' });
    }
}

// CMS webhook configuration
// POST /api/revalidate?secret=your-secret-token
// Body: { "slug": "new-article-title" }

// CMS-side: Trigger revalidation on content update

// Example: Contentful webhook handler
async function handleContentfulWebhook(req, res) {
    const { fields } = req.body;
    const slug = fields.slug?.['en-US'];

    if (slug) {
        await fetch('/api/revalidate?secret=' + process.env.TOKEN, {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({ slug })
        });
    }
}

ISR with Large Datasets

// pages/products/[id].js — ISR for e-commerce
export async function getStaticPaths() {
    // Only pre-build popular products (top 100)
    const popular = await fetch('https://api.example.com/products/popular')
        .then(r => r.json());

    return {
        paths: popular.map(p => ({
            params: { id: String(p.id) }
        })),
        fallback: 'blocking'
    };
}

export async function getStaticProps({ params }) {
    const res = await fetch(`https://api.example.com/products/${params.id}`);
    const product = await res.json();

    if (!res.ok) {
        return { notFound: true };
    }

    return {
        props: { product },
        // Revalidate based on content volatility
        revalidate: product.inStock ? 60 : 300
    };
}

// For the product listing page
export async function getStaticProps() {
    const res = await fetch('https://api.example.com/products');
    const products = await res.json();

    return {
        props: {
            products: products.map(p => ({
                id: p.id,
                name: p.name,
                price: p.price,
                inStock: p.inStock
            }))
        },
        revalidate: 30
    };
}

Common Mistakes

  1. Setting revalidate too low. Revalidating every second defeats the purpose of SSG. Use 60-300 seconds for most content. Lower values mean more server load.
  2. Not using on-demand revalidation for CMS content. Time-based revalidation has a delay window. On-demand revalidation triggers immediately when content changes.
  3. Overlooking the stale-while-revalidate window. During revalidation, stale content is served. If content must never be stale, use SSR.
  4. Revalidating too many paths at once. Batch revalidation requests to avoid triggering too many renders simultaneously.
  5. Not monitoring revalidation health. Set up logging and alerts for revalidation failures. A silent failure means stale content served indefinitely.

Practice Questions

  1. How does ISR differ from traditional SSG?
  2. What does the revalidate property control in getStaticProps?
  3. How does on-demand revalidation work in Next.js?
  4. What is served to users while ISR revalidates a page?
  5. When should you use fallback: 'blocking' with ISR?

Challenge: Build a blog with ISR: implement time-based revalidation (60s), add an on-demand revalidation API endpoint, set up a CMS Webhook simulator, and create a dashboard that shows page generation timestamps.

FAQ

Does ISR still serve static HTML during revalidation?

Yes. ISR serves the previously cached static HTML during revalidation. Users never wait for renders. The fresh page replaces the cache silently.

What happens if the revalidation render fails?

The stale page continues to serve. ISR retries on the next request. Failed revalidations should be logged and monitored.

How does ISR affect build time?

ISR doesn't increase initial build time significantly unless you pre-build many paths. Slow ISR is the revalidation step, which happens in the background after deploy.

Can ISR work with CDN caching?

Yes. Set appropriate Cache-Control headers. ISR integrates with Vercel's edge network and can work with Cloudflare or Akamai using stale-while-revalidate headers.

Does ISR work with static export?

No. ISR requires a server runtime to handle revalidation. Static export (output: 'export') doesn't support ISR. Use Vercel or a custom Node.js server.

Mini Project

Create an ISR-powered news site: implement a homepage that revalidates every 30 seconds, article pages that revalidate every 300 seconds, an admin API endpoint for on-demand revalidation, and a mechanism to display the last-generated timestamp on every page.

What's Next

You understand ISR fundamentals. Now explore Gatsby — a React-based static site framework with its own data layer and plugin ecosystem.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro