Rendering strategies — SSR, SSG, ISR, and CSR
"Where" and "when" a page's HTML actually gets generated is a genuine, consequential choice — not a framework implementation detail. Four real strategies exist, each trading off freshness, speed, and server load differently, and Next.js lets a single app mix all four.
5 min read
The actual question each strategy answers
When does the HTML for this page get generated, and where?
SSG - once, at BUILD time, on the server -> served as a static file forever after
SSR - on EVERY request, on the server -> generated fresh, every single time
ISR - once at build time, THEN regenerated periodically in the background
CSR - never on the server at all -> the browser generates it after JS loads
Every page in a Next.js app picks (explicitly or by default) one of these four strategies, and the choice is a real trade-off between three things: how fresh the data on the page needs to be, how fast the page needs to load, and how much load hitting that page puts on the actual server. There's no single "best" strategy — the right choice depends entirely on what a specific page actually needs.
SSG (Static Site Generation): built once, served forever
// A page with no dynamic data dependency — Next.js generates this
// as a plain HTML file at build time, served identically to every visitor
export default async function AboutPage() {
return <div>About us — this content never changes per-request</div>;
}If a page has no per-request dynamic data (from earlier lessons, the DevReps content-inventory itself is a genuinely static build target since lesson content doesn't change per-visitor), Next.js generates its HTML once, during npm run build, and serves that exact same static file to every visitor forever after — the fastest possible option, since there's zero server computation per request at all, just serving a file. This is the default for any route with no dynamic segments and no explicit opt-out into a different strategy.
SSR (Server-Side Rendering): fresh on every single request
export const dynamic = "force-dynamic"; // opts this page OUT of static generation
export default async function DashboardPage() {
const user = await getCurrentUser(); // depends on WHO is requesting — must run per-request
return <div>Welcome, {user.name}</div>;
}A page whose content genuinely depends on the specific request — the logged-in user, a query parameter, real-time data that can't be stale even briefly — needs SSR: the server runs the page's rendering logic fresh, on every single incoming request, before sending back HTML. This is slower than SSG (real server work happens on every visit, not once ever) but guarantees the HTML is never stale — the correct trade for genuinely per-request content.
ISR (Incremental Static Regeneration): the middle ground
export const revalidate = 60; // regenerate this page's static HTML at most once every 60 seconds
export default async function BlogPost({ params }) {
const post = await db.posts.findUnique({ where: { slug: params.slug } });
return <article>{post.content}</article>;
}ISR gets SSG's speed (visitors are served a pre-built static file, not waiting on a fresh server render) while tolerating data that changes occasionally — revalidate = 60 tells Next.js "this static page can be up to 60 seconds stale; regenerate it in the background after that, and swap in the fresh version for the next visitor once it's ready." This is the right fit for content that changes sometimes but not on every single request — a blog post that might get edited, a product page whose price updates a few times a day — genuinely fresher than pure SSG, genuinely faster than SSR for every visitor except the rare one who happens to trigger the background regeneration.
CSR (Client-Side Rendering): the classic plain-React default
"use client";
import { useEffect, useState } from "react";
function LiveStockPrice() {
const [price, setPrice] = useState(null);
useEffect(() => {
const interval = setInterval(() => fetchPrice().then(setPrice), 1000);
return () => clearInterval(interval);
}, []);
return <p>{price ?? "Loading..."}</p>;
}CSR is what plain React (and this domain's earlier useEffect lesson) has been doing all along: no HTML is generated on the server for this specific dynamic content at all — the browser downloads a mostly-empty shell, runs JavaScript, and that JavaScript is what fetches data and renders the actual content, entirely client-side. This is the right (and often only sensible) choice for content that's genuinely continuous/real-time (a live stock price ticking every second) rather than something meaningfully "generated once per request" — but it means a real, visible delay before the actual content appears, and the initial HTML has nothing in it for search engines or a user with JavaScript disabled to see.
A single Next.js app genuinely mixes all four, page by page
/ (marketing homepage) -> SSG, rebuilt on deploy, content never changes per-visitor
/blog/[slug] -> ISR, revalidated periodically as posts get edited
/dashboard -> SSR, must reflect exactly who's logged in, every request
/dashboard/live-chart -> CSR (a Client Component inside the SSR dashboard page)
ticking every second with fresh data
None of this is an all-or-nothing, app-wide setting — each route (and even each component within a route, via the Server/Client Component split from the previous lesson) picks its own strategy based on what it actually needs. This is the real, practical value of understanding all four: knowing which strategy a given page's requirements actually call for, rather than defaulting to whatever the framework happens to do without thinking about the trade-off being made.
Further reading
Check your understanding
A quick comprehension check — not tracked, not graded, just for you.
1. What's the fundamental difference between SSG and SSR?
2. What does ISR (Incremental Static Regeneration) actually provide that pure SSG and SSR don't?
3. Why is CSR (Client-Side Rendering) the right choice for something like a live, continuously-updating stock price?