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?
answer
- needs the request, not the build
- reading cookies or headers
- searchParams counts, params does not
- one call marks the whole route
- force it with connection() or config
basics
~10 sCalling 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 sNext.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// 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
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.
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.
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.
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