Frontend / Rendering modes / 01_rendering_modes_matrix.md

CSR vs SSR vs SSG vs ISR vs Streaming SSR — The Matrix

Updated 5 interview angles 7 min read source
On this page14
  1. Quick comparison table.
  2. CSR — when does it still fit?
  3. SSR — what’s the trade?
  4. SSG — how is it different?
  5. ISR — the hybrid.
  6. Streaming SSR — what’s new?
  7. How do you pick per route?
  8. What about CDN edge rendering?
  9. Where does state live in each mode?
  10. When does a hybrid app become necessary?
  11. Gotchas
  12. Related
  13. Further reading
  14. Interview angle

CSR vs SSR vs SSG vs ISR vs Streaming SSR — The Matrix

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.

Quick comparison table.

Where and when the HTML is produced — the thing that actually differs:

Mode HTML built Data freshness
CSR in the browser always fresh, fetched on mount
SSR per request, server always fresh
SSG at build time stale until the next build
ISR build, then on demand configurable, e.g. every 60s
Streaming SSR per request, streamed fresh

What that costs you:

Mode TTFB Server cost
CSR fast, but empty minimal, static host
SSR slow, waits for data high, compute per request
SSG fastest, CDN HTML minimal, CDN serves
ISR fast, cached low
Streaming SSR fast, shell first high

SEO is poor only for CSR, which ships an empty page; every other mode ships HTML and indexes well.

State lives on the client for CSR, is baked into the HTML for SSG and ISR, and is server-rendered then hydrated for both SSR modes.

CSR — when does it still fit?

Pure client-side rendering (a Vite SPA; Create React App is deprecated and archived) sends an empty <div id="root"></div> shell + JS bundle. There is nothing to hydrate: JS renders into the empty root (createRoot), fetches data, then renders again.

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.

SSR — what’s the trade?

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.

SSG — how is it different?

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.

ISR — the hybrid.

Incremental Static Regeneration (Next.js-coined term, similar concepts elsewhere): SSG with a revalidation window.

ts
// Next.js
// re-generate at most every 60 seconds
export const revalidate = 60;

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.

Streaming SSR — what’s new?

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:

  1. Server flushes the shell + skeletons (~50ms TTFB).
  2. As each <Suspense> boundary’s data resolves, server flushes that section’s HTML.
  3. Client renders progressively as chunks arrive.
tsx
// 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 Streaming SSR with Suspense — Practical Patterns.

How do you pick per route?

Decision flow:

text
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.

What about CDN edge rendering?

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 Edge Rendering and CDN-as-Runtime.

Use for: lightweight SSR (read-heavy, simple compute), geo-personalized content, A/B testing at the edge.

Where does state live in each mode?

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.

When does a hybrid app become necessary?

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 4 make this per-route choice trivial. SPA-only stacks (plain Vite) need additional infra to serve different routes differently.

Gotchas

  • 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 Hydration — Selective, Progressive, Mismatches.
  • Streaming requires HTTP/1.1+ and a server stack that doesn’t buffer (nginx proxy_buffering off etc.).
  • Personalization on SSG — workarounds: edge-injected user data, client-side personalization layer on top of static base, or move to SSR/ISR.

Further reading

Interview angle 5

  • “Which rendering mode would you choose?” - the wrong question, and saying so is the answer. You pick per route: SSG or ISR for marketing pages, SSR or RSC for anything per-user, and a plain SPA is fine for an admin tool behind auth. A real application is a hybrid.
  • “When is client-side rendering still the right call?” - when SEO does not matter and the audience will tolerate a load, which describes most internal tools and dashboards. You accept a poor LCP in exchange for a much simpler deployment, and for a hundred internal users that is a good trade.
  • “SSR versus SSG - what are you actually trading?” - SSR gives freshness and SEO and costs you TTFB and server capacity on every request. SSG is fast and nearly free to serve and is stale by construction. ISR is the hybrid: serve the cached page, regenerate in the background, which is stale-while-revalidate at page granularity.
  • “What does streaming SSR change?” - the shell flushes immediately and each suspended boundary fills in as its data resolves, so perceived LCP no longer waits on the slowest query. For a data-heavy page it is the largest single improvement available.
  • “Why is hydration cost unavoidable with server rendering?” - because the server serialises the fetched data into the HTML so the client does not refetch on hydration. That payload plus attaching React is real main-thread work, which is why “fast paint, slow first click” is the characteristic SSR failure.