Every page you ship is HTML built from data and components. The four acronyms everyone argues about are just different answers to two questions:
- Where does the HTML get built? On the server, or in the browser?
- When does it get built? Once at build time, on every request, or somewhere in between?
Get those two axes straight and the rest falls into place. Here is each strategy, what it costs you, and when to reach for it. Examples use Next.js App Router, but the concepts apply to any modern framework.
CSR (Client-Side Rendering)
The server sends a near-empty HTML shell plus a JavaScript bundle. The browser runs the JS, fetches data, and builds the page. This is how classic React apps worked before frameworks got involved.
'use client';
import { useEffect, useState } from 'react';
export default function Dashboard() {
const [data, setData] = useState(null);
useEffect(() => {
fetch('/api/metrics').then((r) => r.json()).then(setData);
}, []);
return data ? <Chart data={data} /> : <Spinner />;
}
Good for: highly interactive screens behind auth where SEO does not matter, like dashboards, editors, and internal tools.
The cost: the first paint waits for JS to download, parse, and fetch. Crawlers that do not run JS see an empty page. You pay for it in perceived speed and SEO.
SSR (Server-Side Rendering)
The server builds the full HTML on every request, then ships it ready to display. The user sees content immediately, and the data is fresh because it was fetched moments ago.
export const dynamic = 'force-dynamic';
export default async function Feed() {
const res = await fetch('https://api.example.com/feed', { cache: 'no-store' });
const items = await res.json();
return <FeedList items={items} />;
}
In the old Pages Router this was getServerSideProps. Same idea, different syntax.
Good for: personalized or real-time content. Logged-in pages, anything that depends on cookies or headers, prices that move by the second.
The cost: every request hits your server and your data source. Time to first byte grows under load, and you are paying for compute on each visit.
SSG (Static Site Generation)
The HTML is built once, at build time, and served as static files from a CDN. Nothing runs per request, so it is as fast as the web gets.
export async function generateStaticParams() {
const posts = await getAllPosts();
return posts.map((p) => ({ slug: p.slug }));
}
export default async function Post({ params }) {
const post = await getPost(params.slug); // resolved at build time
return <Article post={post} />;
}
In the Pages Router this was getStaticProps.
Good for: public content that rarely changes. Marketing pages, docs, blog posts, legal pages.
The cost: updating content means a rebuild and redeploy. That is fine for fifty pages. It is painful for fifty thousand, where build times stretch into the territory of "ship a typo fix tomorrow."
ISR (Incremental Static Regeneration)
ISR is SSG that updates itself. Pages are static, but you set a revalidation window. The first visitor after the window expires still gets the instant static page, while the framework regenerates it in the background. The next visitor gets the fresh version. This is stale-while-revalidate applied to whole pages.
export const revalidate = 60; // regenerate at most once per minute
export default async function Product({ params }) {
const product = await getProduct(params.id);
return <ProductView data={product} />;
}
You can also skip the timer and regenerate on demand when data actually changes, using revalidatePath or revalidateTag from a webhook or a server action. That gives you static speed with near-instant updates.
Good for: large catalogs and content that changes periodically but not per request. E-commerce product pages, news, anything with thousands of pages where full rebuilds do not scale.
The cost: there is a window where users may see slightly stale content, and you need hosting that actually supports background regeneration.
The whole picture in one table
| Strategy | Where | When HTML is built | Best for | Main tradeoff |
|---|---|---|---|---|
| CSR | Browser | After load, client-side | Interactive, auth-gated UIs | Slow first paint, weak SEO |
| SSR | Server | Every request | Personalized, real-time pages | Server cost, slower under load |
| SSG | Server (build) | Once at build | Stable public content | Rebuild to update |
| ISR | Server (build + background) | At build, then regenerated | Large or periodically changing catalogs | Possible stale window |
How to choose
Run the page through four questions, in order:
- Is the content unique per user or does it need to be real-time? Use SSR.
- Is it public and rarely changes? Use SSG.
- Is it public, changes periodically, and has too many pages to rebuild each time? Use ISR.
- Is the heavy lifting interactive and client-only after load? Use CSR, usually layered on top of one of the above rather than instead of it.
That last point matters. These are not four camps you pick a side in. Real apps mix them per route: a static marketing homepage, an ISR product catalog, an SSR account page, and CSR for the interactive widgets inside any of them.
Where this is heading
In the App Router, rendering is decided per component, not per page, because Server Components render on the server by default and you opt into the client with 'use client'. Partial Prerendering (PPR) pushes this further: a single page serves a static shell instantly, then streams in the dynamic, personalized parts. The line between these four strategies keeps blurring, but the two questions at the top still tell you what is happening under the hood.
Pick per route, measure, and adjust. There is no single right answer, and that is the point.
Top comments (0)