Chapter 06 — Rendering Strategies: SSR, SSG, ISR & CSR
Chapter 06 — Rendering Strategies: SSR, SSG, ISR & CSR
Hey everyone! You've probably seen these four acronyms thrown around in every Next.js article ever written. Today we make them concrete — what each one actually does, when a page gets which one, and how to force a specific choice when Next.js's automatic decision isn't what you want.
What we will cover:
- The four rendering strategies, explained with timelines
- How Next.js picks one automatically for each route
- Forcing a route to be static or dynamic
generateStaticParams— pre-building dynamic routes- Streaming and why it beats "wait for everything"
1. The Four Strategies — Timelines
CSR — Client-Side Rendering (plain React default): ====================================================== Request → blank HTML shell → download JS → JS runs → fetch data → render User sees: nothing, nothing, nothing... then everything at once. SSR — Server-Side Rendering: ================================ Request → server fetches data → server renders full HTML → send to browser User sees: real content immediately. Re-done on EVERY request. SSG — Static Site Generation: ================================= BUILD TIME: fetch data once → render HTML once → save the file Request → serve the pre-built HTML instantly, no work at request time User sees: instant content. But data is as fresh as the last BUILD. ISR — Incremental Static Regeneration: ========================================== Like SSG, but the HTML automatically re-generates after N seconds (or on-demand). Best of both: instant AND periodically fresh.
2. How Next.js Picks Automatically
You already saw this in the last chapter — Next.js doesn't ask "SSR or SSG?" directly. It infers it from your fetch calls:
No fetch, no dynamic APIs used at all → SSG (static, built once)
fetch(url, { cache: "force-cache" }) → SSG
fetch(url, { next: { revalidate: 60 } }) → ISR (refreshes every 60s)
fetch(url, { cache: "no-store" }) → SSR (fresh every request)
Using cookies(), headers(), or searchParams → SSR (these are request-specific,
forces dynamic automatically)
That last line matters: if your component reads cookies() or the incoming request's headers(), Next.js knows the output CAN'T be the same for every visitor, and automatically makes that route dynamic (SSR) — even if you didn't fetch anything.
3. Forcing a Choice Explicitly
// At the top of a page.js — force the whole route's rendering mode: export const dynamic = "force-static"; // always SSG, ignore dynamic APIs export const dynamic = "force-dynamic"; // always SSR, re-render every request export const revalidate = 3600; // ISR — refresh at most every hour
Use these when you understand the trade-off and Next.js's automatic inference isn't giving you the behavior you want — for example, forcing a mostly-static marketing page to revalidate hourly even though nothing in it obviously changes.
4. Pre-Building Dynamic Routes — generateStaticParams
Dynamic routes like /blog/[slug] are SSR by default (Next.js doesn't know every possible slug in advance). You can tell it the full list, and get SSG instead:
// app/blog/[slug]/page.js
export async function generateStaticParams() {
const posts = await getAllPosts();
return posts.map(post => ({ slug: post.slug }));
// Next.js pre-builds /blog/hello-world, /blog/react-tips, etc.
// at BUILD time — every one of them becomes a static HTML file.
}
export default async function BlogPost({ params }) {
const { slug } = await params;
const post = await getPost(slug);
return <article>{post.content}</article>;
}
Result:
=======
Known slugs at build time → pre-rendered as static HTML (instant)
A slug NOT in the list → Next.js renders it on-demand on first visit,
then caches that result too (by default)
5. Streaming — Don't Make Everyone Wait for the Slowest Thing
SSR has an old problem: if ONE piece of data is slow, the ENTIRE page waits before sending anything. Next.js solves this with streaming, using React's <Suspense>:
// app/dashboard/page.js
import { Suspense } from "react";
export default function Dashboard() {
return (
<div>
<Header /> {/* fast — sent immediately */}
<Suspense fallback={<p>Loading stats...</p>}>
<SlowStats /> {/* slow — streamed in when ready */}
</Suspense>
</div>
);
}
Without streaming: With streaming:
===================== ===================
[ waiting... ] (2s) Header appears instantly
[ everything at once ] "Loading stats..." shows immediately
SlowStats pops in 2s later, on its own
The page becomes USABLE sooner, even though the slow part
still takes just as long underneath.
Key Points to Remember
- CSR = blank shell, JS builds everything in the browser (plain React default)
- SSR = fresh HTML built on the server, every single request
- SSG = HTML built once at build time, served instantly forever
- ISR = SSG that automatically refreshes after a time window or on-demand
- Next.js infers the strategy from your fetch cache options and whether you use request-specific APIs (
cookies(),headers()) generateStaticParamspre-builds dynamic routes like/blog/[slug]as static HTML- Streaming with
<Suspense>lets fast parts of a page appear immediately while slow parts load in afterward
What's Next?
Next chapter: the special files that handle loading, error, and not-found states automatically — including how they connect directly to the streaming/Suspense idea we just covered.
Keep coding, keep learning! See you in the next one!
Post a Comment