NextjsDynamic vs Static Rendering

Dynamic vs Static Rendering

In the Pages Router, you chose your rendering strategy upfront by picking which data function to export: getStaticProps for static generation, getServerSideProps for server rendering on every request. The App Router replaces that upfront choice with inference: Next.js looks at what your route actually does and decides, per route, at build time, whether it can be rendered once statically or whether it must be rendered dynamically on every request.

How the decision gets made

By default, Next.js assumes a route is static. It only switches a route to dynamic rendering when your code does something that genuinely requires request-time information — something that cannot be known in advance at build time.

What your code does

Effect on rendering

Reads cookies() or headers()

Forces dynamic rendering — these are only known per request

Reads the searchParams prop

Forces dynamic rendering — query strings vary per request

fetch(url, { cache: 'no-store' })

Forces dynamic rendering for that route

Uses connection() or other dynamic-only APIs

Forces dynamic rendering

Plain fetch() with default/force-cache, no dynamic APIs used

Route can be rendered statically at build time

Explicit overrides

Inference is convenient, but sometimes you want to be certain rather than hope Next.js infers correctly — for example, right after adding new code, or when you deliberately want one specific behavior regardless of what the route happens to do internally. The route segment config option dynamic lets you force it either way.

app/dashboard/page.tsx — force this route to always render per request

TSX
export const dynamic = 'force-dynamic'

export default async function DashboardPage() {
  const data = await getLiveMetrics()
  return <Dashboard data={data} />
}

app/about/page.tsx — force this route to be built once, statically

TSX
export const dynamic = 'force-static'

export default function AboutPage() {
  return <AboutContent />
}
  • force-dynamic — always render this route on the server for every incoming request, skipping the Data Cache for fetches inside it.

  • force-static — always treat this route as static, even if it contains code that would otherwise force dynamic rendering (cookies/headers return empty values instead of erroring).

  • auto (the default) — let Next.js infer the rendering mode from what the route actually does.

Checking what actually happened

Because the rendering mode is inferred, it is worth actually verifying it rather than assuming. Running a production build prints a route-by-route table showing which routes came out static and which came out dynamic.

next build output

Bash
Route (app)                              Size     First Load JS
┌ ○ /                                    142 B          87.3 kB
├ ○ /about                               142 B          87.3 kB
├ ƒ /dashboard                           1.2 kB         92.1 kB
└ ƒ /products/[id]                       2.4 kB         94.5 kB

○  (Static)   prerendered as static content
ƒ  (Dynamic)  server-rendered on demand
Tip
Get into the habit of scanning the ○ / ƒ column after every build, especially after adding a new fetch call, a cookies()/headers() read, or a new dependency. It is easy to accidentally turn a route you meant to keep static into a dynamic one — usually by pulling in something that reads request-time data deep inside a shared component.
Static and dynamic are not fixed labels
A route's rendering mode is a consequence of its code, not a setting you pick once and forget. Add a `cookies()` call to a component buried three levels deep in an otherwise-static page, and that whole route becomes dynamic on the next build — which is exactly why the build output table is worth checking rather than assuming.