skip to content

A Next.js App Router route is configured with a revalidate value of 60 seconds, and its cached page was generated 90 seconds ago. What does the next visitor receive, and when does fresh HTML first reach a user?

level: middleimportance: should knowfreq 62%

answer

  1. stale-while-revalidate, not blocking expiry
  2. the triggering visitor gets the old copy
  3. regeneration is pull-based, not scheduled
  4. low traffic means unbounded age
  5. nothing cached means the request waits

basics

~20 s

That visitor immediately receives the 90-second-old cached page. Their request marks the entry stale and triggers a regeneration in the background; once it finishes the cached copy is replaced, so a later visitor is the first to see fresh HTML.

solid answer

~50 s

Time-based revalidation is stale-while-revalidate, not an expiry that blocks. The visitor who arrives after the window does *not* wait for a re-render — they are served the existing cached HTML, which is 90 seconds old, and that request triggers a regeneration in the background. When the regeneration completes, it replaces the cached entry, and subsequent visitors get the new page. So the interesting consequence is that at least one user always sees stale content after the window lapses, and on a low-traffic route the page can be far older than the window, because nothing regenerates until someone actually asks for it. The window is a *minimum age before refresh*, not a guarantee of maximum age. If no cached copy exists at all — first ever request, or the entry was evicted — there is nothing stale to serve, so that request renders and waits.

code

tsx · 18 lines
tsx
// app/news/page.tsx
export const revalidate = 60

async function getTopStories() {
  const res = await fetch('https://api.example.com/top-stories')
  return res.json()
}

export default async function NewsIndex() {
  const stories = await getTopStories()
  return (
    <ol>
      {stories.map((s: { id: string; title: string }) => (
        <li key={s.id}>{s.title}</li>
      ))}
    </ol>
  )
}

go deeper

for a junior

Remember the order of events: the visitor gets the existing page instantly, the refresh happens behind them, and the next visitor sees the new content.

for a middle

Explain why this is stale-while-revalidate rather than an expiry, and why regeneration being request-triggered means a low-traffic route can serve content far older than its window.

for a senior

Bring the operational consequences: cold-cache latency on long-tail routes, what happens when a regeneration fails, and how you would pick the window from a stated staleness budget.

for a principal

Own the point at which this mode stops paying — when tolerable staleness approaches render cost you are buying cache complexity for dynamic-rendering economics, and an explicit invalidation path is the better lever.

## The contract A revalidation window on an App Router route says: *serve the stored copy; once it is older than this window, the next request also kicks off a fresh render in the background.* This is the stale-while-revalidate pattern, and the whole point is that the user in front of you never pays for the refresh. Walking the scenario minute by minute: - **t = 0** — the page is rendered and stored. Fresh. - **t = 0 to 60** — every request is served the stored copy directly. Nothing re-renders. - **t = 90** — a visitor arrives. The entry is past its window, so it is *stale*. The visitor still gets it immediately, and Next.js starts a regeneration behind the response. - **shortly after t = 90** — the regeneration finishes and replaces the stored entry. - **the next visitor** — gets the new HTML. So the answer to "who sees fresh content first" is: not the person whose request triggered the refresh. Someone always eats the stale copy. That is the price of never making a user wait. ```tsx // app/news/page.tsx export const revalidate = 60 export default async function NewsIndex() { const stories = await getTopStories() // re-runs during background regeneration return <StoryList stories={stories} /> } ``` ## The consequence people miss: traffic drives regeneration Regeneration is *pull-based*. Nothing runs on a timer waiting for the window to lapse; the work is triggered by a request arriving after it. On a page getting a request every second, the practical staleness really is about a minute. On a page nobody visits for six hours, the stored copy sits there for six hours, and the unlucky visitor at hour six gets six-hour-old content and triggers the refresh for whoever comes next. This is why "revalidate 60" is best read as *"at most one regeneration per minute"* — a rate limit on rebuilding — rather than *"never more than a minute old"*. Candidates who state it as a maximum age are describing a system that would need a scheduler, which this is not. ## The cold case The stale-while-revalidate story assumes there is something stale to serve. When there is not — the very first request to a route that was not prerendered at build, or an entry that has been evicted — the request has nothing to fall back on. It renders on demand and the visitor waits for it, and the result is then stored so later visitors are served from the cache. That is why the first hit on a cold path can be markedly slower than the steady state, and why load tests on a warm cache overstate real-world performance for long-tail pages. If a regeneration *fails* — the upstream API is down, the render throws — the previously stored copy stays in place rather than being replaced by an error. Serving slightly older content beats serving a failure, and that resilience is one of the practical arguments for this mode over rendering per request. ## Choosing the number The window is a product decision converted into seconds: how old may this page be before someone is misled or annoyed? A pricing page might tolerate an hour. A live scoreboard might tolerate five seconds — but at five seconds and meaningful traffic you are regenerating almost continuously, which buys you the complexity of a cache with roughly the cost of rendering per request. When the tolerable staleness approaches the render cost, the honest move is to render per request instead. At the other end, a very long window is fine as long as you have an explicit way to push a change through when it matters, so that an editor publishing something urgent does not have to wait out the clock. ## Assumed version This describes the App Router in Next.js 15 and 16. Caching *defaults* have moved between major versions of Next.js and are a classic source of stale interview answers, but the stale-then-regenerate contract of time-based revalidation itself has been stable: the window controls how often a background refresh may be triggered, and the requester is served the existing copy meanwhile.

  • Does the page regenerate on its own if nobody visits after the window lapses?
    No. Regeneration is triggered by an incoming request that finds the entry stale, not by a timer. A route with no traffic keeps its stored copy indefinitely, so the first visitor after a quiet night can receive content far older than the window and is the one who triggers the refresh.
  • What does a visitor get if the background regeneration fails?
    The previously stored copy stays in place. A failed re-render does not poison the cache with an error page, so users keep seeing the last good version while the failure is retried on a later request. That fallback is a real argument for this mode over rendering per request against a flaky upstream.
  • Why can the first request to one of these routes be much slower than the rest?
    Because stale-while-revalidate needs something stale to serve. If the route was never prerendered or its entry was evicted, there is no stored copy, so the request renders on demand and the visitor waits for the upstream calls. The result is then stored, and subsequent visitors get the fast path.

saying these in an interview costs you the question

  • Says the visitor after the window waits for a fresh render
  • Treats the window as a guaranteed maximum age of the page
  • Believes a timer regenerates the page without any traffic
  • Thinks a failed regeneration replaces the page with an error
  • Assumes every visitor after expiry triggers a separate rebuild

context