Skip to content
C.W.K.
Stream
Lesson 03 of 09 · published

Time-Based Revalidation (ISR)

~20 min · ISR, revalidate, stale-while-revalidate

Level 0Curious
0 XP0/68 lessons0/11 achievements
0/120 XP to next level120 XP to go0% complete

Static speed, fresh-enough data

Time-based revalidation is the App Router's flavor of Incremental Static Regeneration (ISR). Pages are pre-rendered and served from cache; after the revalidate interval, the next request triggers a background re-render. Users still get the cached version instantly — no "loading new content" spinner.

Two scopes

ScopeSetting
Per fetchfetch(url, { next: { revalidate: 60 } })
Per route segmentexport const revalidate = 60 at the top of the page or layout

The lifecycle in one line

Fresh request → render → cache. Within revalidate window → serve cache. After window → serve stale, kick off background re-render. Next request → serve fresh.

Choose the revalidation interval from tolerated staleness, not from the publishing schedule alone. Content edited daily may still need instant publication. In that case, combine a long time window with on-demand invalidation instead of paying for constant background churn. ISR is not a cron job. Crossing the interval does not regenerate the page by itself; the next request initiates the refresh while receiving stale content. A requirement tied to a clock needs a scheduler or webhook. Use a short interval and record the first request, a request inside the window, the first stale request after it, and the following fresh request. Repeat with no traffic and with a failed regeneration so the fallback behavior is understood.

Code

Per-fetch ISR·tsx
export default async function ProductPage({ params }: { params: Promise<{ id: string }> }) {
  const { id } = await params;
  const res = await fetch(`https://api.example.com/products/${id}`, {
    next: { revalidate: 60 },
  });
  return <ProductCard product={await res.json()} />;
}
Per-segment ISR·tsx
// app/blog/page.tsx
export const revalidate = 300;

export default async function BlogPage() {
  const posts = await fetch('https://api.example.com/posts').then(r => r.json());
  return <PostList posts={posts} />;
}
Force a route to one mode·tsx
// app/(marketing)/page.tsx
export const dynamic = 'force-static';   // build once, never re-render

// app/(app)/dashboard/page.tsx
export const dynamic = 'force-dynamic';  // render every request

External links

Exercise

Set up an ISR product page and observe the behavior in dev tools: first request renders, refresh inside the window serves stale, refresh after the window triggers a regeneration on the second hit.

Progress

Progress is local-only — sign in to sync across devices.
Spotted a bug or have feedback on this page?Report an Issue

Comments 0

🔔 Reply notifications (sign in)
Sign inPlease sign in to comment.

No comments yet — be the first.