Next.js supports four rendering strategies — Static (SSG) generates HTML at build time for fastest delivery, Dynamic (SSR) renders per request for fresh data, ISR revalidates static pages on a timer, and Streaming sends HTML progressively with Suspense — the App Router automatically determines the strategy based on your code.
HTML generated at build time, served from CDN — fastest strategy. Default when no dynamic functions are used. Use generateStaticParams for dynamic routes.
HTML generated per request — always fresh data. Triggered automatically by cookies(), headers(), searchParams, or cache: 'no-store'.
Static pages that revalidate after a time interval — stale-while-revalidate pattern. Combines static speed with periodic data freshness.
Progressive HTML delivery via Suspense boundaries — static shell renders immediately, slow data streams in independently.
Next.js determines how and when to render each page. Understanding rendering strategies is essential for balancing performance (speed) with freshness (up-to-date data).
HTML is generated at build time and served from a CDN. This is the fastest rendering strategy — the server does zero work per request.
// This page is static by default (no dynamic functions)
async function BlogList() {
const posts = await fetch('https://api.example.com/posts',
{ cache: 'force-cache' }
);
return <PostList posts={await posts.json()} />;
}Static pages are pre-built during next build. They're ideal for content that doesn't change per user or per request: blog posts, documentation, marketing pages, product listings.
For dynamic routes (/blog/[slug]), use generateStaticParams to tell Next.js which pages to pre-render:
export async function generateStaticParams() {
const posts = await getPosts();
return posts.map(post => ({ slug: post.slug }));
}HTML is generated on the server for each request. The page always has fresh data but is slower than static because the server must render on every request.
A route becomes dynamic when it uses any dynamic function:
cookies() — reads request cookiesheaders() — reads request headerssearchParams — reads URL query parametersfetch() with cache: 'no-store'Next.js automatically detects these and switches to dynamic rendering — you don't need to configure it.
ISR combines static performance with periodic data freshness. Pages are statically generated but revalidate after a time interval:
// Page-level: revalidate every hour
export const revalidate = 3600;
// Or per-fetch:
fetch(url, { next: { revalidate: 3600 } });How ISR works:
next build)This is the stale-while-revalidate pattern — users never wait for regeneration.
Streaming sends HTML progressively as parts of the page become ready, instead of waiting for everything:
import { Suspense } from 'react';
async function Dashboard() {
return (
<>
<Header /> {/* Immediate */}
<Suspense fallback={<ChartSkeleton />}>
<SlowChart /> {/* Streams when ready */}
</Suspense>
<Suspense fallback={<TableSkeleton />}>
<SlowTable /> {/* Streams independently */}
</Suspense>
</>
);
}Each Suspense boundary streams independently. The static shell renders immediately, dynamic parts fill in as they resolve. This dramatically improves Time to First Byte (TTFB) and perceived performance.
Next.js makes this decision automatically based on your code:
revalidate export or next.revalidate → ISR (static with periodic refresh)The decision tree: Can the page be pre-rendered ahead of time? → Static/ISR. Does it need per-request data (user session, real-time)? → Dynamic. Can parts load independently? → Add Streaming.
getStaticProps → Static rendering (default in App Router)getServerSideProps → Dynamic rendering (use dynamic functions)getStaticProps + revalidate → ISR (use export const revalidate)getStaticPaths → generateStaticParamsStatic (SSG) renders at build time — fastest, served from CDN. Dynamic (SSR) renders per request — always fresh. ISR revalidates static pages on a timer — stale-while-revalidate. Streaming sends HTML progressively via Suspense. Next.js automatically determines the strategy based on whether you use dynamic functions (cookies, headers, searchParams, no-store).
Fun Fact
ISR was invented by the Next.js team in 2020 and was considered a breakthrough in the Jamstack world. Before ISR, static sites had to rebuild entirely when content changed — a blog with 10,000 pages would need to rebuild all of them for one post update. ISR made it possible to regenerate individual pages on demand, combining CDN-level performance with CMS-level content management.