Rendering patterns — CSR / SSR / SSG / ISR
CSR, SSR, SSG, ISR. When acronyms line up like this, they look like separate worlds — but the only thing that changes is when and where the HTML gets built. Line them up on that axis and you can see what each one gains and gives up.
What you'll learn in this chapter
- When and where each of the four patterns generates its HTML
- Trade-offs in initial paint, data freshness, SEO, and server load
- The procedure for picking a pattern from your requirements
- That you don't "pick one" — you can mix them on a per-page basis
This chapter uses the premise from Declarative and imperative UI that JavaScript builds the DOM.
What this chapter does not cover
How to actually implement these in Next.js or React is covered by existing guides. This chapter focuses on how to choose a pattern.
- React Guide — Introduction to Next.js — How to write CSR / SSR / SSG
Lining them up on one axis
The further left, the more is done ahead of time — so it's fast. The further right, the more it can use information from the moment of the request — so it's fresh. Do you prioritize speed or freshness? That's what the choice is really about.
CSR — assembled in the browser
The server returns an empty HTML shell and JavaScript, and the browser runs the JavaScript to build the DOM.
<!doctype html>
<html lang="en">
<head>
<title>My App</title>
</head>
<body>
<div id="root"></div>
<script src="bundle.js"></script>
</body>
</html>
Something to note. Even under CSR, HTML still comes back from the server. It's just an empty skeleton — you are not "not fetching HTML at all and only fetching JavaScript". The browser requests the HTML, sees the script tag inside, and goes to fetch the JavaScript. Nothing starts without the HTML.
| Detail | |
|---|---|
| Initial paint | Slow. Wait for HTML → JS download → execute → fetch data → paint |
| Data freshness | Always current. Fetched right before render |
| SEO | Crawlers won't see the content unless they run JavaScript |
| Page transition | Fast. Fetch the diff data and rewrite the DOM |
| Server load | Low. Just serving static files |
Suited to screens where SEO isn't needed and the user keeps interacting — admin panels, dashboards behind a login.
SSR — built on the server for every request
For each request, the server fetches data and returns a fully built HTML.
| Detail | |
|---|---|
| Initial paint | HTML arrives with content in it, so the visual is quick |
| Data freshness | Always current |
| SEO | Favorable. Content is in the HTML |
| Page transition | Plain SSR hits the server every time |
| Server load | High. Data fetching and rendering runs per request |
What people misread is server load and response speed. SSR is not the fastest response pattern, and it does not have the lowest server load. Because it queries the database and assembles HTML per request, time to first byte (TTFB) is longer than SSG or ISR, which have already been built, and the server does more work. What SSR is buying is not speed — it's freshness and SEO.
Also, the HTML that SSR returns is a static string as-is. Buttons don't respond to clicks. It only becomes interactive after JavaScript is loaded and event handlers are attached — the process called hydration. The gap where "you can see it but you can't click it" comes from that. This wait shows up in the responsiveness metric of Core Web Vitals. While hydration runs it occupies the main thread, which stretches that wait further (Event loop).
SSG — everything built at build time
At deploy build time, all pages are generated as HTML and served from a CDN.
| Detail | |
|---|---|
| Initial paint | The fastest. Returned from the CDN edge as a finished product |
| Data freshness | Fixed at build time |
| SEO | Favorable |
| Server load | Minimal. Doesn't even need to reach the origin server |
| Weakness | Content updates require a rebuild. Long builds if there are many pages |
There's a misconception here too. Pages generated by SSG are not "fully static and unable to combine with client-side data fetching". What's generated is ordinary HTML and JavaScript, so after they load, the browser can call an API and swap out parts of the screen.
A common pattern in practice:
- Article body is static-generated by SSG for SEO and speed
- Like counts and comment counts are fetched client-side to stay current
- The header, which depends on login state, is rewritten on the client
"Static skeleton + dynamic parts" is a widely used approach.
ISR — served statically, rebuilt after expiry
SSG's weakness was that "updates need a rebuild". ISR fills that gap. It generates statically at build time, and after the specified time passes, the next request triggers a rebuild of just that page in the background.
In Next.js it looks like this:
// Trigger a regeneration of this page on the first request after 60 seconds
export const revalidate = 60;
export default async function Page() {
const posts = await fetch('https://example.com/api/posts').then((r) => r.json());
return <PostList posts={posts} />;
}
The order of operations matters.
- The page is generated and cached at build time
- Requests within the window get the cache as-is
- The first request after expiry also gets the old cache first (nobody waits)
- Meanwhile, generation of a new page starts in the background
- Once generation succeeds, the cache is swapped. Subsequent requests get the new page
So no one is "kept waiting the instant the cache expires". You'll hit the old content once, but responses stay fast. If regeneration fails, the last successful content keeps being served.
There's also a way to explicitly rebuild in response to an update, rather than waiting for expiry.
| Mode | Usage | Best for |
|---|---|---|
| Time-based | Specify seconds like revalidate = 3600 | Content whose update timing you can't predict |
| On-demand | Call revalidatePath() / revalidateTag() from update logic | Content where updates from an admin UI should reflect immediately |
Next.js's documentation recommends not making the time-based value too short — start around an hour and use on-demand if you need precision, and switch to dynamic rendering if you truly need real-time.
Comparison table
| CSR | SSR | SSG | ISR | |
|---|---|---|---|---|
| When and where HTML is built | Browser | Server | At build time | At build time + regenerated in background |
| Initial paint | Slow | Medium | Fast | Fast |
| Data freshness | Current | Current | At build time | Stale past expiry until the background regeneration finishes |
| SEO | Unfavorable | Favorable | Favorable | Favorable |
| Server load | Low | High | Minimal | Low |
| Reflecting updates | Immediate | Immediate | Rebuild | After expiry or on-demand |
How to choose
Three questions narrow it down.
- Does it need to be indexed by search engines? If no, CSR is enough. Admin panels and post-login screens fall here.
- Does the content change with every request? If it's per-user, or changes by the second, that's SSR.
- Doesn't change, or fine to be slightly stale? Low update rate is SSG; occasional updates are ISR.
An important premise: you are not picking one for the whole app. In frameworks like Next.js, you pick it per page. Real sites almost always mix.
- Top page and article pages → SSG or ISR
- Search results page → SSR
- My-page → CSR
Common misconceptions
"CSR doesn't fetch any HTML from the server on first load." — It does. An empty skeleton of HTML comes back, and the JavaScript is loaded from the script tag in it.
"Building HTML on the server is faster than serving static files." — The other way around. Pre-built SSG is the fastest and has the lowest server load. SSR pays the cost of assembling HTML per request in exchange for freshness and SEO.
"SSG can't combine with client-side data fetching." — It can. What's generated is ordinary HTML and JavaScript, so after loading, you can call an API and update parts.
"With ISR, whoever hits the moment the cache expires gets kept waiting." — They don't. The stale cache is served first, and regeneration runs in the background.
"You pick one rendering pattern per app." — You pick per page. Mixing is normal.
Exercises
Q1. An internal notices page updated once a day, for a few hundred employees. It won't appear in search engines. Which pattern fits?
Answer and explanation
Answer: CSR, or SSG combined with client-side fetching.
Run through the three questions.
- SEO isn't needed (internal) → CSR becomes a candidate
- Changes per request? → No (once a day)
- Fine to be slightly stale? → Yes
SEO isn't needed and the scale is small, so a simple CSR build is reasonable. Behind a login, the benefit of SSR or SSG shrinks even more.
You could also serve just the list skeleton via SSG and fetch the contents from the client. There's no reason in these requirements to pick SSR.
Q2. A media site has tens of thousands of articles, and articles are sometimes edited after publication. SEO is top priority. Do you pick SSG or ISR?
Answer and explanation
Answer: ISR.
SSG has two problems here.
- Building tens of thousands of pages every time makes
next buildimpractical - One article edit means rebuilding and deploying the whole site
ISR generates at build time but rebuilds just the affected page on the request after expiry. If you want edits to reflect immediately, combine with the on-demand mode by calling revalidatePath() from the article update logic.
For SEO, SSG and ISR both put content in the HTML, so there's no difference. What differs is build time and how updates are reflected.
Q3. A page built with SSR "shows up but doesn't respond to clicks" for some time. Why?
Answer and explanation
Answer: Hydration hasn't finished yet.
What SSR returns from the server is fully built HTML, but at that point it's a static string with no event handlers attached. Only after the browser has downloaded and executed the JavaScript and attached the events to the existing DOM — the process called hydration — does it become interactive.
That's where the visual arrives first and interactivity follows behind. This wait affects the responsiveness metric. Mitigations include shrinking the JavaScript bundle and hydrating from the necessary parts first.
Summary
- The difference between the four patterns is when and where the HTML is built
- CSR still returns HTML. It's just an empty skeleton
- SSR is the pattern that buys freshness and SEO. It has neither the fastest response nor the lowest server load
- SSG is the fastest with the lowest server load, but updates need a rebuild. It can still combine with client-side data fetching
- ISR keeps static delivery but, after expiry, serves the stale content while regenerating in the background. On-demand regeneration is also available
- Patterns are chosen per page. Mixing is normal
- React Guide — Introduction to Next.js — Implementation of CSR / SSR / SSG
- MDN — Client-side rendering
- Next.js — Incremental Static Regeneration
What to read next
- Optimistic updates — After rendering, how to make responses to interaction feel fast
- HTTP caching and CDNs — The same "serve stale while regenerating" pattern also exists at the HTTP layer