Next.js gives you four ways to render a page. Pick the right one and you get amazing performance for free. Pick the wrong one and you serve stale data or slow requests.
| Strategy | When Rendered | Pros | Cons |
|---|---|---|---|
| Static (SSG) | At build time | Fastest, cacheable on a CDN | Stale until rebuild or revalidation |
| ISR | At build, then in background on demand | Fast + can update without redeploy | Per-page revalidation only |
| Dynamic (SSR) | Every request | Always fresh | Slowest TTFB; needs warm servers |
| Streaming (with PPR) | Static shell + per-request fragments | Best of both | Newest API surface |
Next.js infers the strategy from what your page does:
cookies() / headers() / searchParams → static."use cache" + tags → ISR-style.experimental_ppr and used <Suspense> for dynamic islands → PPR.You can force a strategy from any page.tsx or layout.tsx:
1// Force static (build will fail if anything dynamic is used)
2export const dynamic = "force-static";
3
4// Force dynamic (every request)
5export const dynamic = "force-dynamic";
6
7// Allow Next.js to decide (default)
8export const dynamic = "auto";1// Revalidate this segment every 60s
2export const revalidate = 60;A blog has three routes:
| Route | Best strategy |
|---|---|
/blog (listing) | Static or ISR (changes occasionally) |
/blog/[slug] (post) | Static — prerendered with generateStaticParams |
/blog/[slug]/comments (recent comments) | Streaming — static shell + dynamic comments island |
/dashboard | Dynamic — per-user content |
A real app mixes all four routinely. Next.js doesn't make you choose one for the whole app.
After next build, you'll see a table like:
Route (app) Size First Load JS
┌ ○ / 1.5 kB 87 kB
├ ○ /about 150 B 85 kB
├ λ /dashboard 320 B 88 kB
└ ● /blog/[slug] 450 B 86 kB
○ (Static) prerendered as static content
● (SSG) prerendered as static HTML (uses getStaticParams)
λ (Dynamic) server-rendered on demand
If you expected /blog to be ○ and you're seeing λ, something on the page is dynamic.
new Date().toISOString() directly in JSX → dynamic in some cases. Wrap in a Client Component or a cached function.fetch with cache: "no-store" → forces dynamic.Use the next build output as the ground truth for what's static and what isn't.