Static and Dynamic Rendering
How Next.js decides whether a route is prerendered once or rendered fresh on every request.
3 min de lectura
Every route in a Next.js app ends up rendered one of two ways, and which one applies isn't something you always declare explicitly — it's often inferred from what your code actually does.
Static rendering
A statically rendered route is rendered once — at build time — and the resulting HTML is reused for every visitor until it's explicitly regenerated. This is the fastest possible way to serve a page: no server work happens per-request, so it can be served straight from a CDN edge location close to the visitor.
// app/about/page.tsx — no request-specific data, so this is a
// perfect candidate for static rendering
export default function AboutPage() {
return (
<div>
<h1>About us</h1>
<p>We've been building developer tools since 2019.</p>
</div>
);
}Next.js renders this once during next build and serves the same HTML to everyone. A route stays eligible for static rendering as long as nothing in it depends on the incoming request.
Dynamic rendering
A dynamically rendered route runs on the server again for every single request. This is required whenever a page's output genuinely depends on that specific request — reading cookies to show a logged-in user's name, reading search params to filter results, or calling an uncached data source that must be fresh.
// app/dashboard/page.tsx
import { cookies } from "next/headers";
export default async function DashboardPage() {
const cookieStore = await cookies();
const theme = cookieStore.get("theme")?.value ?? "light";
return <p>Your theme preference: {theme}</p>;
}Calling cookies() here makes this route dynamic automatically — Next.js can't know the cookie's value ahead of time, so it can't prerender this page even once.
What triggers dynamic rendering
A handful of APIs opt a route into per-request rendering the moment they're used: reading cookies() or headers(), reading the searchParams prop in a page, or making an uncached data request (a fetch without cache: "force-cache" or a revalidate option, called after one of those request-time APIs). None of this requires you to declare anything explicitly — Next.js detects it from the code path.
Forcing a route's rendering mode explicitly
Sometimes the automatic inference isn't what you want. A route-level export overrides it:
// Force this page to always render dynamically, even if nothing
// in it currently reads request-time data
export const dynamic = "force-dynamic";// Force full static rendering, causing a build error if anything
// in the route actually requires per-request data
export const dynamic = "force-static";Reach for force-dynamic on pages you know will always need fresh, per-user data even if today's code happens to look static — a dashboard is a good example, since it's easy to accidentally write a version of it that appears statically renderable until a real user's session data gets added later.
Why the distinction is worth tracking deliberately
Mixing dynamic and static rendering isn't a mistake — most real apps have both. The risk is not noticing which one a given route ended up with. A page you intended to be static but that accidentally reads headers() somewhere deep in its component tree silently becomes dynamic, loses its CDN-cacheable speed, and you may not notice until traffic grows. Running next build shows you exactly which routes were rendered statically versus dynamically — get in the habit of checking that output after significant changes to a route.