Next.js rendering decides whether a route's HTML is built once ahead of time or generated fresh on each user request. The App Router picks static or dynamic rendering automatically based on the APIs each route reads.
Choosing the right strategy balances speed and freshness. Static pages serve straight from a CDN in milliseconds, while dynamic pages always reflect live user state.
flowchart LR
A["Route Evaluation"] --> B{"Uses cookies(), headers(), or no-store?"}
B -- "No" --> C["Static Rendering (Build Time)"]
B -- "Yes" --> D["Dynamic Rendering (Request Time)"]
C --> E{"revalidate = N set?"}
E -- "Yes" --> F["ISR Background Refresh"]
| Strategy | When HTML Builds | Best Fit |
|---|---|---|
| Static Rendering (SSG) | During npm run build |
Docs, marketing pages, landing views |
| Dynamic Rendering (SSR) | On every HTTP request | User dashboards, carts, private feeds |
| Incremental Static Regeneration (ISR) | Built statically, refreshed on interval | Product catalogs, blogs, news listings |
Routes that do not read request-specific data build once during deployment and cache globally.
// app/about/page.tsx - Static Rendering
export default function AboutPage() {
return (
<section className="p-6">
<h2 className="text-2xl font-bold">About Our Platform</h2>
<p className="mt-2 text-gray-600">
This page compiles into static HTML at build time for instant delivery.
</p>
</section>
);
}
Reading cookies(), headers(), searchParams, or calling fetch() with { cache: 'no-store' } switches that route to dynamic rendering automatically.
// app/account/page.tsx - Dynamic Rendering
import { cookies } from 'next/headers';
export default async function AccountPage() {
const cookieStore = await cookies();
const theme = cookieStore.get('theme')?.value ?? 'light';
return (
<section className="p-6">
<h2 className="text-2xl font-bold">Account Settings</h2>
<p className="mt-2">Active theme preference: {theme}</p>
</section>
);
}
Export revalidate from a route file to serve cached static HTML while refreshing stale content in the background after a fixed number of seconds.
// app/articles/[slug]/page.tsx - ISR
export const revalidate = 60;
export default async function ArticlePage({
params,
}: {
params: Promise<{ slug: string }>;
}) {
const { slug } = await params;
const res = await fetch(`https://api.example.com/articles/${slug}`, {
cache: 'force-cache',
});
const article = await res.json();
return (
<article className="p-6">
<h2 className="text-2xl font-bold">{article.title}</h2>
<p className="mt-3">{article.summary}</p>
</article>
);
}
cookies() inside the root layout.tsx: That forces every route underneath the root layout into dynamic rendering.revalidate = 0 everywhere: Setting zero revalidation turns off static caching across routes that rarely change and increases server load.generateStaticParams for static dynamic routes: Without generateStaticParams, dynamic [slug] pages render on first visit instead of at build time.Partial Prerendering serves a static HTML shell immediately while streaming dynamic <Suspense> holes on the same HTTP response.
Export export const dynamic = 'force-static'; inside the route file, though avoid reading cookies() or headers() in that segment.
npm run dev show static vs dynamic caching accurately?No, development mode re-renders routes on every refresh; always test caching behavior with npm run build followed by npm run start.
Sign in to track your learning journey, earn industry-recognized certificates, and join our elite developer community.
Quick Access With