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
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
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
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