CSR vs SSR vs SSG vs ISR vs Streaming SSR — The Matrix
TL;DR
Five (and a half) rendering modes for delivering HTML. CSR — JS renders in the browser, slow first paint, easy infra. SSR — server renders per request, fresh data, server cost. SSG — pre-rendered at build, fastest serve, stale on data change. ISR — SSG with periodic revalidation, hybrid freshness. Streaming SSR — HTML flushed in chunks as data resolves, fastest perceived load. Pick per-route based on: data freshness, SEO need, personalization, server cost budget.
Interview Q&A
Q: Quick comparison table.
A:
| Mode | When HTML is built | TTFB | Data freshness | SEO | Server cost | Where state lives |
|---|---|---|---|---|---|---|
| CSR | in browser (JS) | fast (empty HTML) | always fresh (fetch on mount) | poor (needs SSR for crawlers) | minimal (static host) | client only |
| SSR | per request, on server | slow (waits for data) | always fresh | great | high (compute per request) | server-rendered + client hydrates |
| SSG | at build time | fastest (CDN-cached HTML) | stale (until next build) | great | minimal (CDN serves) | baked into HTML |
| ISR | at build + on demand | fast (cached) | configurable (e.g., revalidate 60s) | great | low (cached) | mostly baked, periodically refreshed |
| Streaming SSR | per request, streamed | fast (shell first) | fresh | great | high | server-rendered + client hydrates |
Q: CSR — when does it still fit?
A: Pure client-side rendering (CRA, Vite SPA, no SSR) sends an empty <div id="root"></div> shell + JS bundle. JS hydrates with empty state, fetches data, then renders.
Pros:
- Cheap infrastructure (static host: S3 + CDN, Vercel static, GitHub Pages).
- Same dev model as building any React/Vue app.
- Authoring is simple.
Cons:
- Bad LCP — user sees blank screen until JS loads + renders.
- Bad SEO — crawlers see empty HTML (some run JS, but unreliable).
- No server data fetch — every page hits the API client-side, no caching at the edge.
Use when: internal tools behind login (SEO doesn’t matter), admin panels, SPAs where the audience tolerates the load wait (e.g., a Trello/Figma-like app).
Don’t use for: marketing sites, blogs, e-commerce, anything that needs SEO or fast first paint.
Q: SSR — what’s the trade?
A: Server renders HTML per request, sends complete page. Client hydrates.
Pros:
- Fast LCP — HTML arrives with content.
- Great SEO — crawlers see real content.
- Fresh data — server fetches on each request.
Cons:
- Slow TTFB — server waits for data before flushing.
- Server cost — compute per request, not cacheable at the CDN.
- Cold start matters in serverless.
Use when: per-request personalization, search results, dashboards, anything needing fresh data + SEO.
Q: SSG — how is it different?
A: Pages are rendered at build time, output as static HTML files, deployed to a CDN. Same URL → same HTML for everyone, until the next build.
Pros:
- Fastest serve — CDN cache hit, nothing to compute.
- Minimal server cost — pure static hosting.
- Predictable perf — no cold starts, no DB latency.
Cons:
- Stale on data change — until next build.
- Build time grows with page count — a 100K-product e-commerce site can’t SSG every page.
- No personalization — same HTML for all users.
Use when: marketing pages, docs, blogs with weekly cadence, low-frequency content.
Q: ISR — the hybrid.
A: Incremental Static Regeneration (Next.js-coined term, similar concepts elsewhere): SSG with a revalidation window.
// Next.js
export const revalidate = 60; // re-generate at most every 60 seconds
export default async function Page() {
const post = await fetchPost();
return <Article post={post} />;
}
Behavior:
- First request: render + cache.
- Within 60s: serve cached.
- After 60s: serve cached + trigger background re-render. Next request gets the fresh one.
This is stale-while-revalidate at the page level. Combines SSG’s speed with bounded staleness.
Use when: blog/news/product pages that update occasionally but don’t need instant freshness.
Q: Streaming SSR — what’s new?
A: SSR that flushes HTML in chunks as data resolves, instead of waiting for everything.
Without streaming: server waits for all data → renders complete HTML → flushes → ~1500ms TTFB.
With streaming + Suspense:
- Server flushes the shell + skeletons (~50ms TTFB).
- As each
<Suspense>boundary’s data resolves, server flushes that section’s HTML. - Client renders progressively as chunks arrive.
// React + Next.js App Router
<Suspense fallback={<HeaderSkeleton />}>
<Header />
</Suspense>
<Suspense fallback={<FeedSkeleton />}>
<Feed /> {/* slow data */}
</Suspense>
Result: user sees the shell immediately, sections fill in as they load. Best perceived LCP for data-heavy pages.
See 04_streaming_ssr_with_suspense.md.
Q: How do you pick per route?
A: Decision flow:
Is data per-user / behind auth?
├── Yes
│ └── Is SEO needed? → No → CSR or SPA
│ → Yes → SSR (per-request render)
└── No
└── Does data change frequently (sub-minute)?
├── Yes → SSR or streaming SSR
└── No
└── Does it change at all between builds?
├── Yes (hourly/daily) → ISR
└── No (or rebuild on change) → SSG
Streaming SSR is the modern upgrade to SSR — same trade-offs, better perceived perf.
Q: What about CDN edge rendering?
A: SSR / ISR rendered at the CDN edge (close to the user) instead of a central data center. Lower latency, but limited compute (no full Node), often constrained DB access.
Platforms: Vercel Edge Functions, Cloudflare Workers, Netlify Edge, AWS Lambda@Edge. See 07_edge_rendering.md.
Use for: lightweight SSR (read-heavy, simple compute), geo-personalized content, A/B testing at the edge.
Q: Where does state live in each mode?
A:
| Mode | Server state | Client state |
|---|---|---|
| CSR | none (JS fetches from API) | full client state |
| SSR | rendered into HTML + serialized for hydration | hydrates from serialized payload |
| SSG | baked into HTML at build | hydrates from baked data |
| ISR | rendered into HTML + revalidated | hydrates from current snapshot |
| Streaming | streamed into HTML + serialized hydration data inline | hydrates from streamed payload |
| RSC | RSC payload (separate format) | client components hydrate |
The senior insight: server-rendered modes “serialize and hydrate” — the data fetched on the server is sent as JSON-ish payload alongside HTML so the client doesn’t refetch. Frameworks (Next.js, Nuxt, Remix) handle this automatically; understanding the mechanism explains why hydration cost is real.
Q: When does a hybrid app become necessary?
A: Almost always. Real-world: marketing pages (SSG/ISR), blog (ISR), product pages (SSR or ISR), dashboard (RSC), admin (SPA/CSR), API routes (server functions). One mode forced everywhere → wrong trade-offs on most routes.
Next.js App Router and Nuxt 3 make this per-route choice trivial. SPA-only stacks (CRA, plain Vite) need additional infra to serve different routes differently.
Gotchas / edge cases
- CSR + SEO — Google generally executes JS, but other crawlers may not. Don’t rely on it for critical SEO routes.
- SSR + cold starts — serverless platforms can have 100ms-2s cold starts. Edge functions are faster but more limited.
- SSG with millions of pages — build time blows up. Use ISR with on-demand revalidation, or split builds.
- ISR cache stampede — first request after revalidation triggers regeneration; concurrent requests during regen get the stale version (usually) or the new one (varies by platform).
- Hydration mismatch between SSR/SSG output and client render — see ../05_react/hydration.md.
- Streaming requires HTTP/1.1+ and a server stack that doesn’t buffer (nginx
proxy_buffering offetc.). - Personalization on SSG — workarounds: edge-injected user data, client-side personalization layer on top of static base, or move to SSR/ISR.
What a senior is expected to say
- “No single right mode — pick per route. Marketing pages SSG/ISR; per-user routes SSR or RSC; admin tools can stay SPA; the hybrid is the realistic answer.”
- “CSR is fine when SEO doesn’t matter and audience tolerates load wait — admin tools, behind-auth dashboards. Bad LCP, bad SEO.”
- “SSR fresh + SEO + slow TTFB + server cost. SSG fast + cheap + stale. ISR is the SWR-at-page-level hybrid.”
- “Streaming SSR is the modern upgrade — shell streams immediately, suspended boundaries fill in. Best perceived LCP for data-heavy pages.”
- “Server-rendered modes serialize fetched data into the HTML so the client doesn’t refetch on hydration. That’s why hydration cost is real.”
- “Next.js App Router and Nuxt 3 are the production-ready hybrids today. SPA-only stacks are a hard constraint, not a choice.”
Cross-references
- React Server Components: 03_rsc_deeper.md
- Hydration models: 02_hydration_models.md
- Streaming SSR practical: 04_streaming_ssr_with_suspense.md
- Next.js App Router rendering control: 05_nextjs_rendering_control.md
Further reading
- web.dev — Rendering on the web: https://web.dev/articles/rendering-on-the-web
- Next.js — Rendering: https://nextjs.org/docs/app/building-your-application/rendering
- Nuxt — Rendering modes: https://nuxt.com/docs/guide/concepts/rendering