Incremental Static Regeneration (ISR)
Updating static pages without rebuilding your entire site
🔄 What is ISR?
ISR combines the speed of static pages with the freshness of server-side rendering. It lets you update static content after deployment without rebuilding, perfect for content that changes occasionally but needs to stay fast and scalable.
// Revalidate page every 60 seconds
export const revalidate = 60
export default async function Page() {
const data = await fetch('https://api.example.com/data')
return <div>{data.title}</div>
}
ISR Concepts
ISR allows static pages to be regenerated in the background after a specified time interval. Users get fast static pages while content stays fresh. When revalidation time expires, the next visitor triggers a background update, keeping your site current without manual rebuilds.
Static Speed
Fast as static pages
// Served from cache
Instant load times
Auto Updates
Content refreshes automatically
// Background regeneration
revalidate: 60
Scalable
Handle millions of pages
// Generate on-demand
fallback: 'blocking'
Cost Efficient
No constant rebuilds needed
// Update only when needed
Smart regeneration
🔹 Basic ISR Example
Set a revalidation time to automatically update static pages:
// app/products/page.js
export const revalidate = 3600 // Revalidate every hour
async function getProducts() {
const res = await fetch('https://api.example.com/products')
return res.json()
}
export default async function ProductsPage() {
const products = await getProducts()
return (
<div>
<h1>Our Products</h1>
{products.map(product => (
<div key={product.id}>
<h2>{product.name}</h2>
<p>${product.price}</p>
</div>
))}
</div>
)
}
// Page regenerates in background after 1 hour
How it works:
- Page generated at build time
- Served as static HTML (fast!)
- After 1 hour, next request triggers regeneration
- Old page shown while new one generates
- New page replaces old one when ready
🔹 ISR with Dynamic Routes
Use ISR with dynamic routes for scalable content:
🔸 Blog Post Example
// app/blog/[slug]/page.js
export const revalidate = 60 // Revalidate every minute
export async function generateStaticParams() {
const posts = await fetch('https://api.example.com/posts')
.then(r => r.json())
// Generate first 100 posts at build time
return posts.slice(0, 100).map(post => ({
slug: post.slug,
}))
}
export default async function BlogPost({ params }) {
const post = await fetch(`https://api.example.com/posts/${params.slug}`)
.then(r => r.json())
return (
<article>
<h1>{post.title}</h1>
<p>{post.content}</p>
<time>{post.publishedAt}</time>
</article>
)
}
// Other posts generated on-demand
🔹 Revalidation Strategies
Different ways to configure ISR revalidation:
🔸 Time-based Revalidation
// Revalidate every 10 seconds
export const revalidate = 10
// Revalidate every hour
export const revalidate = 3600
// Revalidate every day
export const revalidate = 86400
🔸 Per-fetch Revalidation
// Different revalidation times for different data
async function getData() {
// Revalidate products every hour
const products = await fetch('https://api.example.com/products', {
next: { revalidate: 3600 }
})
// Revalidate reviews every 5 minutes
const reviews = await fetch('https://api.example.com/reviews', {
next: { revalidate: 300 }
})
return { products, reviews }
}
🔸 On-Demand Revalidation
// app/api/revalidate/route.js
import { revalidatePath } from 'next/cache'
export async function POST(request) {
const path = request.nextUrl.searchParams.get('path')
if (path) {
revalidatePath(path)
return Response.json({ revalidated: true })
}
return Response.json({ revalidated: false })
}
// Call: POST /api/revalidate?path=/blog/my-post
🔹 ISR with Fallback Pages
Handle pages not generated at build time:
Dynamic Rendering Options:
- dynamicParams = true (default): Generate new pages on-demand
- dynamicParams = false: Return 404 for unknown pages
// app/products/[id]/page.js
export const revalidate = 3600
export const dynamicParams = true // Allow new pages
export async function generateStaticParams() {
// Generate top 50 products at build time
const products = await getTopProducts(50)
return products.map(p => ({ id: p.id }))
}
export default async function ProductPage({ params }) {
const product = await getProduct(params.id)
if (!product) {
notFound() // Show 404 page
}
return <div>{product.name}</div>
}
// Other products generated when first visited
🔹 When to Use ISR
ISR is perfect for specific scenarios:
✅ Perfect for ISR:
- E-commerce: Product pages with occasional updates
- Blogs: Articles that may be edited after publishing
- News sites: Stories that need periodic updates
- Documentation: Docs that change occasionally
- Marketing pages: Landing pages with A/B testing
❌ Not ideal for ISR:
- Real-time data: Use SSR instead
- User-specific: Personalized content needs SSR
- Never changes: Pure SSG is simpler
- Changes constantly: SSR is better
🔹 ISR Best Practices
Optimize your ISR implementation:
- Choose appropriate revalidation times: Balance freshness vs. performance
- Generate popular pages at build: Use generateStaticParams for top content
- Use on-demand revalidation: For immediate updates when content changes
- Monitor cache hit rates: Ensure ISR is working effectively
- Handle errors gracefully: Show stale content if regeneration fails
// Good revalidation times
export const revalidate = 60 // News: 1 minute
export const revalidate = 300 // Products: 5 minutes
export const revalidate = 3600 // Blog: 1 hour
export const revalidate = 86400 // Docs: 1 day
🔹 ISR vs SSG vs SSR
Understanding when to use each rendering method:
| Feature | SSG | ISR | SSR |
|---|---|---|---|
| Speed | ⚡ Fastest | ⚡ Fast | 🐢 Slower |
| Freshness | ❌ Static | ✅ Periodic | ✅ Always |
| Cost | 💰 Cheapest | 💰 Low | 💰💰 Higher |