Incremental Static Regeneration — Updating Static Content Without Full Rebuilds
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
- 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.
- Not using on-demand revalidation for CMS content. Time-based revalidation has a delay window. On-demand revalidation triggers immediately when content changes.
- Overlooking the stale-while-revalidate window. During revalidation, stale content is served. If content must never be stale, use SSR.
- Revalidating too many paths at once. Batch revalidation requests to avoid triggering too many renders simultaneously.
- Not monitoring revalidation health. Set up logging and alerts for revalidation failures. A silent failure means stale content served indefinitely.
Practice Questions
- How does ISR differ from traditional SSG?
- What does the
revalidateproperty control in getStaticProps? - How does on-demand revalidation work in Next.js?
- What is served to users while ISR revalidates a page?
- 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
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