Next.js's rendering options are often explained as a historical list (the old Pages Router had getStaticProps/getServerSideProps) rather than what actually differs about when the HTML gets generated — which is the detail that actually matters for choosing correctly.
The core question: when does rendering happen?
Every Next.js rendering strategy answers one question differently: does this page's HTML get generated at build time, at request time, or somewhere in between?
SSG (Static Site Generation)
The page's HTML is generated once, at build time, and served identically to every visitor until the next deploy.
export default async function BlogPost({ params }) {
const post = await getPost(params.slug); // runs at build time
return <article>{post.content}</article>;
}
export async function generateStaticParams() {
const posts = await getAllPosts();
return posts.map((post) => ({ slug: post.slug }));
}Best for: content that doesn't change per-visitor and doesn't need to reflect changes faster than your deploy cadence — blog posts, documentation, marketing pages.
SSR (Server-Side Rendering)
The page's HTML is generated fresh on every single request, on a real server, using data that can be as current as the moment of the request.
export const dynamic = "force-dynamic";
export default async function Dashboard() {
const data = await getLiveMetrics(); // runs on every request, fresh
return <MetricsPanel data={data} />;
}Best for: genuinely per-request or per-user data — a live dashboard, personalized content, anything reading cookies or headers to render differently per visitor. The cost: a real server is required, and every request pays the full render cost.
ISR (Incremental Static Regeneration)
A middle ground — the page is generated at build time like SSG, but Next.js will regenerate it in the background after a specified interval, so it's periodically refreshed without needing a full site rebuild or paying SSR's per-request cost.
export const revalidate = 3600; // regenerate at most once per hour
export default async function ProductPage({ params }) {
const product = await getProduct(params.id);
return <ProductDetail product={product} />;
}Best for: content that changes, but not so often that every visitor needs the absolute latest version — product prices, article view counts, anything where "up to an hour stale" is an acceptable tradeoff for not needing a server-render on every request.
Static export: no server, ever
output: "export" is a stricter mode than plain SSG — it requires every route in the entire app to be resolvable at build time, produces plain HTML/CSS/JS files with zero server runtime, and can be hosted on any static file host (S3, GitHub Pages, any CDN) with no Node.js process running at all.
// next.config.ts
const nextConfig = {
output: "export",
};This site itself runs this way — every route, including dynamic ones like /blog/[slug], is pre-rendered into ./out at build time via generateStaticParams, with no server ever running in production. The tradeoff: SSR and ISR are simply unavailable — there's no server to render per-request or revalidate on a schedule, so anything needing genuinely live data has to fetch it client-side after the static page loads instead.
| Aspect | SSG / Static export | SSR |
|---|---|---|
| When HTML is generated | Once, at build time | Fresh, on every request |
| Requires a server in production | No (static export) / minimal (SSG) | Yes, always |
| Data freshness | As fresh as the last deploy (or revalidation, with ISR) | Always current as of the request |
| Hosting | Any static file host or CDN | A Node.js server or serverless platform |
Choosing correctly
- Does the content genuinely need to be different per-request or per-user? If yes, SSR (or client-side fetching from a static page) — nothing else can give you that.
- Does it change, but slowly, and staleness of minutes-to-hours is fine? ISR — if your hosting isn't static-export-constrained.
- Does it not need to be fresher than your deploy cadence? SSG — the simplest, cheapest, fastest option.
- Do you want zero server infrastructure at all? Static export — accepting that anything genuinely live has to be fetched client-side after the page loads, the same tradeoff behind serving frequently-changing data (like live headlines or prices) from a static site without triggering a full rebuild on every update.
On-demand revalidation: a middle ground within ISR
Beyond a time-based revalidate interval, Next.js also supports triggering revalidation explicitly, on demand, when content actually changes — useful for a CMS-backed site that wants near-instant updates without falling back to full SSR:
// A route handler triggered by a CMS webhook when content is published
import { revalidatePath } from "next/cache";
export async function POST(request: Request) {
const { path } = await request.json();
revalidatePath(path);
return Response.json({ revalidated: true });
}This gives ISR's caching benefits (fast, cached responses most of the time) with SSR-like freshness exactly when content actually changes — the page stays cached and fast until a real update happens, at which point it's explicitly invalidated rather than waiting out a fixed time interval.
Common mistakes
- Defaulting to SSR for content that doesn't actually need per-request freshness. This adds real infrastructure cost and latency for content that would render identically as SSG or ISR.
- Assuming ISR works under
output: "export". Static export has no server to run the background revalidation — every route behaves like plain SSG (computed once, at build time) regardless of arevalidatevalue set on it. - Fetching genuinely static content client-side with
useEffectinstead of rendering it server-side or at build time — this adds an unnecessary loading state and network waterfall for data that didn't need to be fetched in the browser at all. - Mixing rendering strategies without understanding the tradeoff being made per route — picking SSR "to be safe" everywhere loses the real performance and cost benefits SSG/ISR provide for content that didn't need per-request freshness.
Related reading
- Getting Started with the Next.js App Router — shares tags: nextjs, react, web-development (same category).
- Next.js Middleware: What It's For and When to Avoid It — shares tags: nextjs, react, web-development.
- React Server Components Explained: What They Are and When to Use Them — shares tags: react, nextjs, web-development.
- React State Management: useState, useReducer, and When You Actually Need a Library — shares tags: react, web-development.
- Core Web Vitals Explained: What Actually Affects Your Score — shares tags: web-development.