skip to content

Static vs Dynamic Rendering Triggers

You rarely pick dynamic rendering explicitly — one call to cookies() or headers() picks it for you. Interviewers ask why a page that used to be static suddenly renders on every request, and the answer is always a dynamic API or a no-store fetch.

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

explore

questions

5

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%

answer

  1. needs the request, not the build
  2. reading cookies or headers
  3. searchParams counts, params does not
  4. one call marks the whole route
  5. force it with connection() or config

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.

solid answer

~40 s

Next.js prerenders a route at build time unless something in it needs the request. The things that need the request are the Dynamic APIs: `cookies()`, `headers()` and `draftMode()` from `next/headers`, `connection()` from `next/server`, and the `searchParams` prop passed to a `page`. Since Next 15 those are asynchronous, so you `await` them. Touching any one of them anywhere in the route's tree — including a shared layout — marks the whole route dynamic, not just the component that called it. Notably, reading the `params` prop does not, because Next can enumerate those paths ahead of time; adding `'use client'` does not either, since Client Components are prerendered to HTML too. You can also force it explicitly with `export const dynamic = 'force-dynamic'`, `export const revalidate = 0`, or `await connection()`.

code

typescript · 5 lines
typescript
// app/page.tsx — static: nothing here needs the request
export default async function Page() {
  const posts = await getPosts()
  return <ul>{posts.map(p => <li key={p.id}>{p.title}</li>)}</ul>
}

go deeper

for a junior

Be able to name the Dynamic APIs — cookies(), headers(), draftMode(), connection(), searchParams — and say plainly that touching one makes the page render on every request instead of at build time.

for a middle

Explain that the decision is per route, so a call in a layout or an imported module counts, and that params and 'use client' are not triggers. Know these APIs are awaited since Next 15.

for a senior

Show you check the build output rather than guessing, and that you can trace an unexpected dynamic route back to the module that reads the request, including one buried in a shared package.

for a principal

Own the position that rendering mode is a property teams keep drifting, and that caching defaults have moved between Next majors — so codify the intent per route instead of relying on a remembered default.

## Two ways Next.js can render a route In the App Router a route is rendered in one of two modes. **Static rendering** (prerendering) runs the route's Server Components once — at build time, or later when a revalidation happens — and stores the resulting HTML plus the RSC payload, which every visitor then receives. **Dynamic rendering** runs the route's Server Components on each incoming request. Next.js picks the mode for you. The default is "as static as possible": if nothing in the route needs to look at the request, it gets prerendered. You therefore rarely choose dynamic rendering deliberately — you *trip* it, usually with a single line of code someone added for an unrelated reason. ## What trips it: the Dynamic APIs These are the APIs whose whole purpose is reading something that only exists once a request exists: - `cookies()` — from `next/headers` - `headers()` — from `next/headers` - `draftMode()` — from `next/headers` - `connection()` — from `next/server`, which exists precisely to say "wait for a real request before continuing" - the `searchParams` prop a `page` receives Since Next 15 the `next/headers` functions and the `params` / `searchParams` props are asynchronous and must be awaited: ```tsx // app/dashboard/page.tsx import { cookies } from 'next/headers' export default async function Page() { const theme = (await cookies()).get('theme')?.value return <main data-theme={theme}>Dashboard</main> } ``` That one `await cookies()` is enough. This page will now be rendered for every request. ## What does *not* trip it Several things get blamed for dynamic rendering and are innocent: - **The `params` prop of a dynamic segment.** Segment values are enumerable ahead of time, so a `[slug]` route can still be prerendered. - **`'use client'`.** Client Components are still rendered to HTML during a static render; they simply also ship JavaScript. A client-heavy page can be fully static. - **`useSearchParams()` in a Client Component.** This does not make the *server* render dynamic. On a statically rendered route it causes the Client Component tree up to the nearest `<Suspense>` boundary to be rendered on the client instead, which is a different (and often subtler) problem. - **Simply calling `fetch`.** Fetching data is not by itself a request-time dependency. ## Forcing it on purpose Sometimes you want dynamic rendering with no Dynamic API in sight — a page that must show a fresh random pick, or must never be cached. Three explicit levers: - `export const dynamic = 'force-dynamic'` on the page or layout - `export const revalidate = 0` on the page or layout - `await connection()` at the top of the component, which is the surgical version ## The blast radius is the route Static versus dynamic is decided **per route**, and a route is the composition of the root layout, every nested layout, and the page. A Dynamic API called in *any* of them — or in any module they import, including a third-party SDK — makes the whole route dynamic. There is no partial credit in the classic model: you cannot have one dynamic paragraph in an otherwise prerendered page. (Partial Prerendering exists to change exactly that, and is its own topic.) ## The moving part: data-fetch caching defaults This is where stale interview answers come from. In Next 14, `fetch` responses were cached by default and passing `cache: 'no-store'` was itself a way to switch a route to dynamic rendering. Next 15 changed the default so `fetch` is no longer cached unless you ask. Next 16 additionally ships an opt-in Cache Components mode that inverts the model further — you mark what should be cached rather than what should be dynamic. Because the defaults have moved between majors, reason from the mechanism ("does this need the incoming request?") and name the version you assume rather than quoting a default from memory. The Dynamic API list above has been the stable part throughout. ## How to tell which mode a route got `next build` prints a per-route summary marking each route Static or Dynamic. That summary — not intuition — is how you check a route is still prerendered after a change, and it is the first place to look when server load jumps unexpectedly.

  • Does reading the params prop of a dynamic segment force dynamic rendering too?
    No. Segment values can be enumerated ahead of time, so a `[slug]` route is still prerendered for the paths Next knows about — `params` is not a Dynamic API. Requests for paths outside that set are rendered on demand, which is a routing decision rather than a route-wide switch to dynamic rendering.
  • Why did Next 15 make cookies(), headers(), params and searchParams asynchronous?
    Making them promises lets Next begin rendering a component before request data is available and only suspend at the point the value is actually read. That is what allows the request-independent parts of a tree to be produced ahead of time instead of blocking the whole render on request data.
  • How would you force dynamic rendering on a route that calls no Dynamic API at all?
    Either `await connection()` from `next/server` inside the component that must run per request, or a segment-level `export const dynamic = 'force-dynamic'` / `export const revalidate = 0`. `connection()` is the narrower tool: it states the actual requirement — this render must wait for a real request — rather than reconfiguring the segment.

saying these in an interview costs you the question

  • Thinks only the component that called cookies() renders per request
  • Says useSearchParams() in a Client Component makes the server render dynamic
  • Believes adding 'use client' forces a route to render dynamically
  • Confuses dynamic segments like [id] with dynamic rendering
  • Assumes any fetch call in a page opts the route out of prerendering

context

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

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

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

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