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:

  1. Page generated at build time
  2. Served as static HTML (fast!)
  3. After 1 hour, next request triggers regeneration
  4. Old page shown while new one generates
  5. 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

🧠 Test Your Knowledge

What does ISR stand for?