skip to content

Rendering Modes

Every Next route is prerendered at build, regenerated on a timer, or rendered per request, and choosing wrong shows up as stale content or a slow first byte. Interviewers want you to defend the choice, not just recite the acronyms.

part ofNext.jsoverview, primer and where to startread it →
on this pageshow

explore

questions

18

In a Next.js App Router app, what is the difference between a route that is prerendered at build time and one that is rendered on every request, in terms of when its data is read and what each visitor receives?

level: juniorimportance: must knowfreq 72%

answer

  1. one question: when does the component run
  2. build once for everyone, or once per visit
  3. data frozen at build vs read now
  4. static file vs server invocation per request
  5. freshness paid for in latency and cost

basics

~20 s

A prerendered route fetches its data once during the production build and serves the same stored HTML to every visitor. A per-request route re-runs that code on each visit, so each visitor gets markup built from data read at that moment.

solid answer

~50 s

Both routes can be written with exactly the same async Server Component code; what differs is *when* that code runs. A prerendered route runs at `next build`: its data is fetched once, the resulting HTML and RSC payload are written to disk, and every visitor afterwards is handed those same bytes, so the data is as old as the build. A per-request route runs the component on the server for each incoming request, so the data is fresh at that instant, but the visitor waits for whatever the render and its upstream calls cost. The practical consequences follow from that: prerendered output is a static asset that any cache or CDN can serve identically to everyone, while per-request output is unique to that request and cannot be shared blindly. Both still ship the same client JavaScript — the mode decides when HTML is produced, not whether the page is interactive.

code

tsx · 17 lines
tsx
// app/pricing/page.tsx
// Identical source; the rendering mode decides when this runs.
async function getPlans() {
  const res = await fetch('https://api.example.com/plans')
  return res.json()
}

export default async function PricingPage() {
  const plans = await getPlans()
  return (
    <ul>
      {plans.map((p: { id: string; name: string }) => (
        <li key={p.id}>{p.name}</li>
      ))}
    </ul>
  )
}

go deeper

for a junior

Be ready to say plainly that the difference is when the component runs — once at build, or once per request — and that prerendered pages serve the same HTML to everyone.

for a middle

Explain the mechanics: what artifacts a build produces, why a prerendered response needs no server invocation, and how staleness arises from data frozen at build time.

for a senior

Show that you reason per route rather than per app, and connect the choice to real numbers: server cost per request, time to first byte, and how long stale content is tolerable for that page.

for a principal

Own the framing that rendering mode is a freshness-versus-cost contract per surface, and be able to argue when paying for per-request renders is genuinely worth it against the operational simplicity of static output.

## The one thing that actually differs In the App Router, a page is written as an async Server Component that fetches what it needs and returns JSX. That source code does not say when it runs. The rendering mode is the answer to exactly one question: **at what moment does Next.js execute this component?** There are two moments. - **Build time.** During `next build`, Next.js executes the component, collects the HTML plus the serialized RSC payload for the route, and writes them out as files. This is prerendering, historically called SSG (static site generation). - **Request time.** Next.js runs the component on the server once per incoming HTTP request and streams the result back. This is dynamic rendering, historically called SSR (server-side rendering). Everything else people say about "static vs dynamic" is a downstream consequence of that one difference. ## What the visitor receives With a prerendered route, every visitor receives byte-identical HTML. The tenth visitor and the ten-thousandth get the file produced at build. Nothing on the server needs to run to answer the request, so the response can come from a plain file server or a CDN edge, and the time to first byte is essentially network latency. With a per-request route, the visitor receives HTML that was produced for them. The server executed the component, awaited whatever it awaited, and sent the result. The response can reflect anything known about that request, and it costs a server invocation plus the latency of the slowest upstream call the component makes. ## When the data is read This is the part juniors most often get wrong. Prerendering does **not** mean "no data". A prerendered page may call a database or an API — it just calls it once, during the build, and the answer is frozen into the output: ```tsx // app/pricing/page.tsx export default async function Pricing() { const plans = await getPlans() // runs at build for a prerendered route, return <PlanTable plans={plans} /> // and per request for a dynamic one } ``` If `getPlans()` returns something different an hour after the build, a prerendered page keeps showing the old answer until the site is rebuilt or the route is configured to revalidate. That staleness is not a bug; it is the deal you accepted in exchange for a static file. ## What prerendering is not Two confusions are worth naming explicitly. **It is not client-side rendering.** A prerendered page still arrives as complete HTML — the content is in the document, which is why it is good for crawlers and for first paint. Client-side rendering ships an empty shell and fills it in the browser; that is a different thing entirely. **It is not "no JavaScript".** Any Client Components on the page are still bundled, still shipped, and still become interactive in the browser. A prerendered marketing page with an interactive pricing toggle is perfectly normal. Choosing a rendering mode decides when the HTML was manufactured, not whether the page has behaviour. ## Why the choice is per route, not per app Next.js decides this route by route — really segment by segment — rather than for the whole application. The same deployment can hold a prerendered marketing homepage, a per-request account dashboard, and article pages that sit somewhere in between. That is deliberate: freshness requirements differ per page, and forcing one mode across the app means either paying for server renders you did not need or shipping stale data where it matters. ## The tradeoff in one line Prerendering trades freshness for cheap, uniform, cacheable responses. Per-request rendering trades cost and latency for data that is correct at the instant of the request. Most real answers in an interview start by asking which of those two the page actually needs, and the honest answer is often "neither extreme" — which is what time-based revalidation exists to cover.

  • If the underlying data changes an hour after the build, what does a prerendered route show?
    The build-time data, indefinitely. A prerendered route has no idea the source changed; it keeps serving the stored HTML until something replaces it — a new deployment, a configured revalidation window, or an explicit invalidation. If an hour of staleness is unacceptable, prerendering with no revalidation is the wrong mode for that page.
  • Does a prerendered page still become interactive in the browser?
    Yes. Prerendering only fixes when the HTML was produced. Any Client Components on that page are bundled and shipped as usual, so buttons, menus and forms work exactly as they would on a per-request page. The choice affects freshness, cost and cacheability of the HTML — not whether the page has client behaviour.

saying these in an interview costs you the question

  • Thinks a prerendered page cannot contain any JavaScript or interactivity
  • Believes statically generated pages update themselves when the database changes
  • Confuses build-time prerendering with client-side rendering of an empty shell
  • Assumes the whole app must pick one rendering mode
  • Says per-request rendering means the data is fetched in the browser

context

open as a page

In the Next.js App Router, a page with no special configuration is prerendered at build time. Which APIs, when a Server Component in that route calls them, switch the whole route to rendering on every request instead?

level: juniorimportance: must knowfreq 72%

basics

~10 s

Calling a Dynamic API — cookies(), headers(), draftMode() from next/headers, connection() from next/server, or reading a page's searchParams prop — opts the entire route out of prerendering, so Next.js renders it per request.

open as a page

In a Next.js App Router route segment, what does adding a loading.tsx file cause the framework to do, and which part of the UI does its output replace while data loads?

level: juniorimportance: must knowfreq 82%

basics

~20 s

Next.js wraps that segment's page.tsx and everything below it in a Suspense boundary whose fallback is loading.tsx's default export. The layout above stays on screen, and the fallback also appears instantly on client navigation into the route.

open as a page

A Next.js App Router app has three pages: a marketing landing page, a news index that must be at most a minute out of date, and a signed-in account dashboard. Which rendering mode would you choose for each, and what drives each decision?

level: middleimportance: must knowfreq 78%

basics

~20 s

Prerender the landing page at build time, give the news index a short revalidation window so it regenerates roughly every minute, and render the dashboard per request because its output depends on who is asking. The driver is whether output varies per request, then how stale it may be.

open as a page

A Next.js App Router app adds a shared header in app/layout.tsx that awaits cookies() to greet the signed-in user. After that change, every route in the build output is listed as Dynamic, including the marketing pages. Explain why one call in the layout affects unrelated pages, and how you would get those pages prerendered again.

level: middleimportance: must knowfreq 55%

basics

~20 s

Next.js decides static versus dynamic per route, and a route is the composition of the root layout plus every nested layout and the page. The root layout is part of every route, so its cookies() call opts them all out of prerendering.

open as a page

A Next.js App Router page shows a fast product summary and a slow reviews list. The segment has a loading.tsx, so the whole page area sits behind the skeleton until the reviews query finishes. How do you make the summary appear while only the reviews keep loading?

level: middleimportance: must knowfreq 64%

basics

~20 s

loading.tsx is segment-granular: one fallback for the entire page. Move the reviews query into its own component, render it inside a <Suspense> boundary in page.tsx, and keep page.tsx itself free of that await so the summary can flush immediately.

open as a page

A Next.js App Router page mixes static marketing copy with a personalized, per-user cart badge. What does Partial Prerendering (PPR) produce for that route, and how does it differ from rendering the whole route dynamically?

level: juniorimportance: should knowfreq 40%

basics

~20 s

Partial Prerendering produces one static HTML shell for the route at build time, with holes where the personalized parts go, then streams those holes into the same response at request time — rather than rendering the whole route per request.

open as a page

In a Next.js App Router route with Partial Prerendering enabled, what decides which parts of the component tree land in the prerendered static shell and which become dynamic holes?

level: middleimportance: should knowfreq 30%

basics

~20 s

The <Suspense> boundary is the seam. Anything the build can resolve becomes shell HTML; a subtree that reads request-time data must sit inside a boundary, so its fallback goes in the shell and its children are postponed to request time.

open as a page

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%

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.

open as a page

The Next.js App Router lets a page or layout export a route segment config named dynamic. What are its four values, and what does each one do to a page that calls headers()?

level: middleimportance: should knowfreq 46%

basics

~10 s

The values are 'auto', 'force-dynamic', 'force-static' and 'error'. Auto lets headers() opt the route into dynamic rendering; force-dynamic renders per request regardless; force-static prerenders and makes headers() return empty values; error fails the build.

open as a page

An e-commerce Next.js App Router app serves 50,000 product pages from a /products/[slug] route. How do you decide whether to prerender all of them at build time, and what are your options when a full build takes too long?

level: seniorimportance: should knowfreq 48%

basics

~20 s

Prerender the whole catalogue only if the build stays within an acceptable deploy time and the slug set is known. Otherwise return just the high-traffic slugs from generateStaticParams and let the rest render on first request and be cached from then on.

open as a page

After a routine dependency upgrade, a Next.js App Router route that used to be listed as Static in the build output is now listed as Dynamic, and origin traffic has jumped. How do you track down what made it dynamic, and what do you verify before shipping the fix?

level: seniorimportance: should knowfreq 40%

basics

~20 s

Scope it from the build output, then find the request-time read: set export const dynamic = 'error' on the segment so the build fails and names the call, since the culprit is usually cookies() or headers() inside an upgraded shared package rather than in your own files.

open as a page

A Next.js App Router route has a loading.tsx, but its layout.tsx awaits a slow API call before rendering. Nothing at all reaches the browser for two seconds — not even the skeleton. Why does loading.tsx not help here, and how would you fix it?

level: seniorimportance: should knowfreq 48%

basics

~20 s

Next.js nests the boundary created by loading.tsx inside layout.tsx, so the layout renders above the fallback. Work awaited in the layout blocks the shell itself. Move that fetch into a child component wrapped in its own <Suspense> inside the layout.

open as a page

A team has made every route in their Next.js App Router app render on each request because it is simpler and always fresh. As the engineer leading the app, how do you evaluate that default and decide which routes to pull back to static or revalidated rendering?

level: principalimportance: should knowfreq 38%

basics

~20 s

Treat it as a cost and resilience decision, not a style preference. Inventory routes by whether output depends on the requester, their freshness tolerance and their traffic share, then convert the highest-traffic request-independent routes first and measure the change.

open as a page

You own a Next.js App Router route whose main data takes roughly two seconds. How do you decide between streaming a skeleton with loading.tsx and letting the response block until the data is ready?

level: principalimportance: should knowfreq 34%

basics

~20 s

Stream when the shell carries real value the user can act on, and when committing to a 200 response early is acceptable. Block when the page is meaningless without the slow data, or when the response still needs to change its status, headers, or destination.

open as a page

A team turns on Partial Prerendering for a Next.js product route, but the build output still reports the route as fully dynamic and production TTFB is unchanged. How would you diagnose it?

level: seniorimportance: nice to knowfreq 20%

basics

~20 s

Check four things in order: whether the route is actually opted in, whether a request-time read sits above every Suspense boundary (often in a layout), whether a segment config forces dynamic rendering, and whether a proxy is buffering the stream and erasing the benefit.

open as a page

Partial Prerendering in Next.js has shipped as an experimental, opt-in feature. As the engineer deciding for a production app, how do you weigh adopting it?

level: principalimportance: nice to knowfreq 15%

basics

~20 s

Weigh three things: an experimental flag can change shape between releases, the failure mode is graceful because a route simply reverts to normal rendering, and the real cost is restructuring components around Suspense boundaries — a refactor that pays off even without PPR.

open as a page

You lead a Next.js App Router codebase where routes keep drifting from static to dynamic as features land, and nobody notices until the hosting bill does. What conventions and guardrails would you put in place, and what tradeoffs would you accept?

level: principalimportance: nice to knowfreq 26%

basics

~20 s

Make rendering mode an explicit, enforced property of each route: guard the load-bearing static routes so a request-time read fails the build, diff the build's route classification in CI, and confine request-scoped reads to modules the team recognises as dynamic.

open as a page