skip to content

A Next.js App Router page is cached with a one-minute time-based revalidation window. Hours after the underlying data changed, a visitor loads the page and still sees the old content; they reload once and now see the new content. Explain what Next.js served on each of those two requests.

level: middleimportance: should knowfreq 55%

answer

  1. the cache answers first, refreshes second
  2. expiry is a condition, not an event
  3. someone always eats the stale response
  4. one regeneration, many stale readers
  5. serve stale while revalidating in the background

basics

~20 s

The first request found an expired cache entry, was served the stale copy immediately, and triggered a background regeneration. That regeneration finished and replaced the entry, so the reload was served the newly rendered page.

solid answer

~50 s

Time-based revalidation in Next.js serves stale while it revalidates. The window expired long before either request, but nothing acts on an expired entry until traffic arrives. The first visitor's request found the entry stale, so Next.js answered it from the old stored payload — nobody is made to wait for a fresh render — and kicked off a regeneration in the background. That render fetched current data and replaced the cache entry. By the time the reload came in, the entry was fresh, so it served the new content. This is why "one visitor always sees stale data after a change" is expected behaviour, not a bug: the first request after expiry is the trigger, and it pays with staleness rather than with latency. If a change must never be shown stale even once, a window is the wrong mechanism and on-demand invalidation is the right one.

go deeper

for a junior

Know that an expired cached page is still served to the visitor who triggers the refresh, so seeing old content once right after a change is normal. Say that the reload after it shows the updated page.

for a middle

Walk the request timeline out loud: stale served, regeneration scheduled, entry replaced, next request fresh. Be able to explain why nothing happened during the hours before any request arrived.

for a senior

Demonstrate production judgment — how many users actually eat the stale response, what happens when regeneration fails silently, and how you would instrument renders to prove the mechanism rather than guessing at a CDN.

for a principal

Own the policy question: which content may ever be served stale once, which must be invalidated on the event, and how you keep a window as a backstop without letting it become the only correctness guarantee.

## The serving rule With a time-based window, a cached route in Next.js is in one of two states when a request arrives: **fresh** (age below the window) or **stale** (age at or past it). Fresh entries are served as-is. Stale entries are also served as-is — and the arrival of that request additionally schedules a regeneration. The response the user gets and the work the server does are two separate things happening on the same request. This is the stale-while-revalidate shape familiar from HTTP caching, applied to a rendered route: the cache prefers to answer instantly with something slightly old rather than make a user wait for fresh work. ## Walking the timeline in the question - **T+0** — the upstream data changes. Next.js has no idea; nothing pushed it that information, and no request has come in. - **T+1 minute** — the window elapses. Still nothing happens. Expiry is a *condition*, not an event. - **T+2 hours** — the first visitor arrives. The entry is stale, so it is served to them verbatim. That is the old content they complained about. Simultaneously a regeneration starts: the page's server code runs again and fetches current data. - **A moment later** — the regeneration completes and atomically replaces the stored entry, resetting its age. - **The reload** — the entry is now fresh, so the visitor is served the new render. The two hours in the middle are not Next.js being slow. They are simply the gap before any request existed to notice the expiry. ## Concurrency If a burst of requests hits the moment the entry goes stale, they are all served the stale copy while a single regeneration runs; Next.js does not spawn one render per request for the same route. That is a deliberate protection: expiry on a hot route is a thundering-herd risk against your data source, and serving stale with one in-flight refresh de-duplicates the work. It also means the number of visitors who see stale content after an expiry is not one — it is however many arrive during the regeneration. ## Failure behaviour If the regeneration throws — the CMS is down, a query times out — the stored entry is not replaced. The old copy keeps being served and the next request retries. The system degrades to "content stops updating", never to "the page errors". This is usually the behaviour you want in production, but it has an operational consequence: a broken data source can look like nothing at all from the outside, so revalidation failures deserve their own logging or alerting rather than being inferred from user reports. ## Why this trips teams up The support ticket is always some version of "I published, waited, refreshed, and it was still old — then I refreshed again and it was right." The instinct is to blame a CDN or the browser. The actual explanation is the serving rule above, and the diagnostic is straightforward: log inside the page's data-fetching function so you can see when a render actually runs, then correlate that with the two requests. A render that runs on the request that showed old content is the confirmation. ## The contrast with on-demand invalidation On-demand invalidation (`revalidatePath` / `revalidateTag` from `next/cache`) exists precisely to remove the "one cohort sees stale" step. Rather than letting an entry age out and be served once more, it marks the entry invalid at the moment of the change, so the next request re-renders instead of being handed the old payload. The cost is the reverse tradeoff: someone pays render latency instead of receiving stale content, and you need a trigger — a publish webhook, a mutation path — that reliably fires. Many production setups run both: on-demand invalidation for correctness the instant something changes, and a time-based window as a backstop for the invalidation events that get dropped. ## Answering crisply Name the rule ("stale-while-revalidate: expired entries are served, and serving them triggers the refresh"), walk the two requests, and then say what you would change if one stale response is unacceptable. That last sentence is what separates an explanation from an engineering answer.

  • How many visitors see the stale copy after a window expires — exactly one?
    No. Everyone who arrives between the expiry-triggering request and the completion of the regeneration is served stale. On a busy route with a slow render, that can be a large cohort. Next.js de-duplicates the work to a single in-flight regeneration rather than one per request, which protects the data source but does not shorten the stale window for readers.
  • How would you prove this is what is happening rather than a CDN caching the response?
    Log inside the page's server-side data fetching, so you can see exactly when a render executes. If a render fires on the request that returned old content, and the following request returns new content without a render, you have confirmed the stale-then-refresh sequence. A CDN-level cause would instead show no origin render at all on either request.
  • The product owner says a price change may never be shown stale, not even once. What do you change?
    Stop relying on the window for correctness. Invalidate on the event that changes the price — a Route Handler the pricing system calls, invoking `revalidateTag` or `revalidatePath` — so the entry is marked invalid at the moment of the change and the next request re-renders rather than being served the old payload. Keep a window as a backstop for missed events.

saying these in an interview costs you the question

  • Says the first request after expiry waits for a fresh render
  • Thinks the page refreshes itself when the window elapses
  • Assumes exactly one user ever sees stale content
  • Blames the browser cache or CDN without checking the origin
  • Believes a failed regeneration replaces the page with an error

context