# CSR vs SSR vs SSG (/docs/engineering-judgment/decision-guides/csr-vs-ssr-vs-ssg)



## TL;DR [#tldr]

A fast-growing e-commerce brand migrated its storefront to a pure Client-Side Rendered (CSR) Single Page Application to give developers a unified React workflow. The migration triggered a business disaster: organic search rankings plummeted 60% within weeks because search engine crawlers deferred or dropped the empty initial HTML shell. Worse, mobile shoppers on average 3G connections stared at a blank white screen for 4 full seconds before the massive JavaScript bundle finished downloading and parsing—sending bounce rates through the roof. Choosing a web rendering model is not about framework fashion; it is an engineering calculation balancing Time to First Byte (TTFB), First Contentful Paint (FCP), the interactive hydration gap, and server infrastructure overhead.

> 💡 &#x2A;*Rule of thumb:** Choose rendering strategies by route and data lifecycle, not by application-wide dogmatism. Prefer static generation (SSG/ISR) when the same generated representation can be reused across many requests and its freshness can be maintained by publishing or revalidation; use request-time server rendering (SSR) when useful initial HTML must include request-time state that cannot safely or acceptably be represented by a shared static response; and center client-side rendering (CSR) for private, authenticated workspaces where the initial document shell is generic and value unfolds in a rich client session.

* **SSG delivers edge cacheability and instant first paint:** Pre-generating HTML at build time or via background revalidation achieves sub-50ms TTFB worldwide, shielding database clusters from request-time traffic surges.
* **SSR embeds request-time state on the initial network hop:** Server-rendering delivers fully populated semantic HTML for immediate crawler indexing and human perception, but places rendering CPU and database queries directly onto the critical request path.
* **CSR offloads page-generation work to the browser:** Serving a static shell reduces server compute costs, but forces users to pay a heavy upfront download, parse, and execution tax for client JavaScript bundles before seeing useful content.
* **Mind the interactive hydration gap:** SSR and SSG deliver early visual content (FCP), but complex applications can trap users in an "uncanny valley" where buttons look clickable but remain completely unresponsive until client hydration finishes.
* **Fatal pitfall:** &#x2A;*Defaulting to SSR for high-traffic public catalog routes without caching.** Hitting un-memoized database queries on every incoming page render turns your web servers into an instant scalability bottleneck under traffic spikes. Always decouple public layouts into cached static shells (SSG/ISR) and fetch personalized, volatile fragments asynchronously via client APIs.

<TermBox term="CSR / SSR / SSG">
  These labels describe **where and when useful HTML is produced**.

  * **CSR (client-side rendering):** the browser runs application JavaScript to create or update the useful UI.
  * **SSR (server-side rendering):** the server creates useful HTML for a particular request.
  * **SSG (static site generation / prerendering):** useful HTML is generated before an individual request and reused later.

  **Why it matters here:** the decision is about data timing, cacheability, latency, client work, and operational ownership—not which acronym is newest.
</TermBox>

## Decision frame [#decision-frame]

Before naming a framework, write down the constraints:

* What must be present in the initial HTML before application JavaScript runs?
* Is that initial representation public and shared, or does it depend on the current request/user?
* How stale may the representation become before the product is wrong or misleading?
* Is the page mainly reading/navigation, or a long-lived interactive workspace?
* Which responses can be reused safely through HTTP/CDN caching, and what changes the cache key?
* What latency budget exists between request arrival and useful HTML?
* How much client JavaScript is required before the important interactions work?
* Which failures can the team operate reliably: build/revalidation failures, request-time rendering failures, client data-loading failures, or some combination?

<TermBox term="Request-time state">
  **Request-time state** is information only known, trusted, or selected when a particular request arrives—for example the authenticated user, request cookies, authorization result, geography, or data that must be current for that request.

  **Why it matters here:** if useful initial HTML truly depends on request-time state, one shared prebuilt representation may not be sufficient or safe.
</TermBox>

Those answers narrow the choice more reliably than a framework feature checklist.

<AtlasIllustration id="rendering-strategies-timeline" />

## The options [#the-options]

### Client-side rendering (CSR) [#client-side-rendering-csr]

With CSR, the server usually sends a generic HTML shell plus JavaScript. The browser runs application code, obtains data, and creates or updates the useful UI.

Choose CSR as the center of gravity when the initial HTML does not need request-specific application state and the product already depends on a long-lived client session, rich local state, and repeated API interaction. The cost is explicit: initial usefulness depends more heavily on JavaScript download/execution and client-side data acquisition.

### Server-side rendering (SSR) [#server-side-rendering-ssr]

With SSR, the server produces useful HTML for a request and may incorporate request-time data. Interactive applications commonly execute client JavaScript afterward to attach behavior to that server-rendered HTML.

<TermBox term="Hydration">
  **Hydration** is the process where client-side application code attaches interactive behavior to HTML that was already rendered on the server.

  **Why it matters here:** SSR can deliver useful HTML before the client runtime is ready, but interactive regions may still depend on JavaScript download, execution, and successful client activation. In React, the initial client output must match the server-rendered markup closely enough for `hydrateRoot()` to hydrate it correctly.
</TermBox>

<AtlasIllustration id="hydration-gap" />

Choose SSR when useful initial HTML must include request-time state, or when moving data access into the server request path avoids an unacceptable browser-to-API waterfall. Rendering then becomes part of the request path, so capacity, cache policy, latency, timeout behavior, and rendering failures become production concerns.

### Static site generation / prerendering (SSG) [#static-site-generation--prerendering-ssg]

With SSG, HTML is produced before an individual request, usually during a build, publish, or revalidation process. The generated artifact can then be served without performing a full page render for every request.

<TermBox term="Revalidation">
  In static-generation systems, **revalidation** is a process that refreshes or regenerates previously produced content when its freshness rules say it should be updated.

  **Why it matters here:** static does not have to mean “never changes.” The design question is whether publishing or revalidation can keep shared generated content within the product's allowed staleness window.
</TermBox>

<AtlasIllustration id="isr-lifecycle" />

Choose SSG when many requests can safely receive the same generated representation and the allowed staleness window can be met through publishing or revalidation. Request-specific or continuously changing regions need another mechanism.

<DecisionMatrix
  caption="CSR vs SSR vs SSG decision matrix"
  options="['CSR', 'SSR', 'SSG']"
  rows="[
  {
    criterion: 'Request-specific initial HTML',
    values: [
      'Initial shell is usually generic; request-specific data arrives after client code runs',
      'Can include request-time state in the initial HTML',
      'Needs a dynamic layer when the initial response must differ per request',
    ],
  },
  {
    criterion: 'Very fresh data',
    values: [
      'Client can fetch current data after startup',
      'Server can fetch current data on the request path, subject to cache policy',
      'Requires sufficiently frequent publish/revalidation or a dynamic/client data path',
    ],
  },
  {
    criterion: 'Useful HTML before app JavaScript runs',
    values: [
      'Limited to what the shell already contains',
      'Available when the server render succeeds',
      'Available from the generated artifact',
    ],
  },
  {
    criterion: 'Long-lived application interactivity',
    values: [
      'Client runtime naturally owns the ongoing UI session',
      'Still requires client activation for interactive regions',
      'Still requires client JavaScript for interactive regions',
    ],
  },
  {
    criterion: 'Shared response reuse',
    values: [
      'Shell/assets can be shared; personalized API data is separate',
      'Depends on which request attributes vary the rendered response and cache key',
      'High when the same generated representation can be reused',
    ],
  },
  {
    criterion: 'Page-generation work on each request',
    values: [
      'No full page render is required on the server; APIs may still do request-time work',
      'Present unless a cached rendered response is reused',
      'Absent for normal delivery after generation; revalidation/publishing does the generation work',
    ],
  },
  {
    criterion: 'Freshness mechanism',
    values: [
      'Client data requests and client cache policy',
      'Request-time data access and/or cached SSR',
      'Build, publish, or revalidation plus optional dynamic regions',
    ],
  },
  {
    criterion: 'Primary operational burden',
    values: [
      'Client bundle/data-loading/state failures plus backend APIs',
      'Latency-sensitive render capacity, data access, caching, and client activation',
      'Build/revalidation correctness plus any dynamic escape hatches',
    ],
  },
]"
/>

<TermBox term="Cache key">
  A **cache key** is the information used to decide which stored response may satisfy a request.

  **Why it matters here:** an SSR response that varies by user, cookie, locale, or authorization state cannot be safely shared unless the caching design includes the relevant variation. SSG is most reusable when many requests genuinely share the same representation.
</TermBox>

## When each option fits [#when-each-option-fits]

### Prefer CSR when [#prefer-csr-when]

* the product is an authenticated dashboard, editor, or workspace where most value appears after interaction begins;
* the initial HTML does not need user-specific application state;
* rich client state and repeated API interactions are already central to the product;
* the team can keep JavaScript size, loading states, errors, and client performance within product budgets.

A CSR choice does **not** mean "put all logic in the browser." Authentication, authorization, durable state, and trust boundaries remain backend concerns.

### Prefer SSR when [#prefer-ssr-when]

* useful initial HTML must include request-specific or rapidly changing state;
* delivering that HTML before the client runtime becomes ready matters to the user experience;
* request-time server data access avoids an otherwise unacceptable client-side data waterfall;
* the team can operate rendering as part of the latency-sensitive request path.

<TermBox term="Critical request path">
  The **critical request path** is the chain of work that must finish before a request can produce the user-visible result you care about.

  **Why it matters here:** SSR moves rendering and often data access onto that path. Slow serial data work, render capacity, or cache misses can therefore directly increase response latency.
</TermBox>

SSR does not guarantee a fast page. Slow server data fetching, serial work, poor caching, or a large client activation payload can still dominate latency and interactivity.

### Prefer SSG when [#prefer-ssg-when]

* many requests can receive the same initial representation;
* publishing or revalidation can satisfy the freshness contract;
* broad CDN/cache reuse is safe for that representation;
* avoiding page-generation work on the normal request path simplifies operations or improves latency consistency.

Documentation, marketing pages, public reference material, and some catalog/detail pages often meet these conditions. The decision still depends on their actual freshness and personalization requirements.

## Hybrid rendering is normal [#hybrid-rendering-is-normal]

<AtlasIllustration id="hybrid-rendering-architecture" />

The labels describe **where particular rendering work happens**, not mutually exclusive application identities.

A product can statically generate public documentation, server-render a request-specific account page, and run a client-rendered editor after navigation. A single route can also start from shared or server-rendered HTML and then use client-side data fetching for regions that update continuously.

The useful design unit is therefore the **surface and its data**, not an architecture acronym for the whole product.

## Failure modes and hidden costs [#failure-modes-and-hidden-costs]

### Treating SEO as the only reason for server/static HTML [#treating-seo-as-the-only-reason-for-serverstatic-html]

Search visibility can matter, but useful HTML before a large client runtime is ready can also matter for user experience. Conversely, a public page does not automatically require SSR; shared static HTML may satisfy the same requirement with less request-time work.

### Ignoring hydration and client activation cost [#ignoring-hydration-and-client-activation-cost]

An e-commerce team switched their product catalog from SSG to pure SSR to display live inventory badges. Under normal traffic, TTFB looked acceptable at 180ms. But during a marketing campaign, the SSR server made 4 unmemoized database queries per page render. Under 5,000 req/sec, the rendering servers overloaded the database, TTFB spiked from 180ms to 9.2 seconds, and the site effectively went down—even though 95% of the page content was identical for every visitor:

* **Impact:** The store suffered a 45-minute outage during peak shopping hours, causing severe revenue loss and damaging search rankings.
* **Root cause:** Abusing whole-page SSR for a small, volatile data field (stock counts), placing un-cached database queries directly onto the synchronous HTML render path.
* **Correct pattern:** Adopt a **hybrid rendering architecture**:
  1. Serve the shared product catalog skeleton via SSG/ISR from edge cache with TTFB \< 50ms.
  2. Fetch dynamic inventory status asynchronously on the client (CSR) using a short-lived cache (e.g. 10 seconds) or a lightweight WebSocket connection.

Server-rendered HTML can still require substantial JavaScript before interactions work. React's `hydrateRoot()` requires the initial client output to match the server-rendered markup; mismatches are bugs, and React does not guarantee every mismatch will be patched automatically.

### Assuming static means stale forever [#assuming-static-means-stale-forever]

Static generation is a production strategy, not a promise that content never changes. Publishing, revalidation, and targeted dynamic/client data paths can provide controlled freshness.

### Assuming CSR removes server complexity [#assuming-csr-removes-server-complexity]

The browser may render the UI, but APIs still need authentication, authorization, caching, rate limits, observability, and failure semantics. CSR moves rendering work; it does not remove backend engineering.

## Practical heuristic [#practical-heuristic]

<Mermaid
  chart="graph TD
  Start[&#x22;Evaluate UI surface & data requirements&#x22;] --> Shared{&#x22;Can useful initial HTML be shared across all users?&#x22;}
  Shared -- Yes --> Freshness{&#x22;How quickly does content change?&#x22;}
  Freshness -- &#x22;Rarely (Docs, Marketing, Catalog)&#x22; --> SSG[&#x22;Static Site Generation (SSG)<br/>Edge CDN cached; rebuild on publish&#x22;]
  Freshness -- &#x22;Minutes to hours&#x22; --> ISR[&#x22;SSG with Revalidation (ISR)<br/>Shared cache with background refresh&#x22;]
  Shared -- No --> RequestState{&#x22;Must initial HTML contain user-specific data?&#x22;}
  RequestState -- &#x22;Yes (SEO / Fast First Paint needed)&#x22; --> SSR[&#x22;Server-Side Rendering (SSR)<br/>Render per request; watch TTFB & DB load&#x22;]
  RequestState -- &#x22;No (Authenticated workspace/editor)&#x22; --> CSR[&#x22;Client-Side Rendering (CSR)<br/>Generic static shell + client API fetching&#x22;]"
/>

Use these questions in order:

1. **Can the same initial representation be reused across many requests?** If yes, test whether static generation plus caching can satisfy the freshness requirement.
2. **Must useful initial HTML include request-time state?** If yes, consider SSR for that surface.
3. **Can the initial document be generic while most value appears during a long client session?** If yes, CSR may be the simplest center of gravity.
4. **Can the route be split by data lifecycle?** Keep shared/stable regions static or cached and use request-time/client work only where their data requirements demand it.
5. **What fails when this rendering path fails?** Choose a model whose generation, caching, data access, and client-activation failures the team can detect and recover from.

## Check your mental model [#check-your-mental-model]

> **Scenario:*&#x2A; A SaaS startup is launching an analytics dashboard. Users log in to view charts generated from their private telemetry data. The marketing VP insists: &#x2A;"We must use Server-Side Rendering (SSR) for every dashboard route so Google can index our customer reports and improve our SEO ranking."*
>
> **How would you evaluate this reasoning in an architecture review?**

<details>
  <summary>
    Show the reasoning
  </summary>

  **Why this reasoning conflates distinct requirements:**

  1. **Search engines do not index authenticated private pages:** Search crawlers (such as Googlebot) do not log in as specific authenticated users. A private analytics dashboard behind an auth wall will never appear in public search results regardless of whether it is rendered via SSR or CSR.
  2. **Unnecessary server load:** Using SSR for a private, data-dense dashboard forces server compute onto every page view, requiring database queries on the critical request path.
  3. **Better architecture:** Use a fast, static generic shell (CSR) cached on a CDN, with an authenticated API or GraphQL endpoint fetching telemetry data asynchronously after page load. Reserve SSR and SSG for public marketing and landing pages where SEO indexing and unauthenticated first-load performance actually matter.
</details>

## Decision review checklist [#decision-review-checklist]

Use this checklist during architecture design reviews before committing to a rendering strategy:

* [ ] **First visual need:** What content must be visible in HTML before application JavaScript executes?
* [ ] **Data boundary:** Which initial data is public/shared, request-specific, or continuously changing?
* [ ] **Allowed staleness:** What is the product-acceptable staleness window, and can publishing/revalidation satisfy it?
* [ ] **Cache keys & variation:** Which responses can be cached safely, and what request headers/cookies vary their cache key?
* [ ] **Client execution budget:** How much client JavaScript bundle size and CPU execution time are required before interactive elements respond?
* [ ] **Critical request path:** What database or upstream API calls are placed directly onto the user-facing latency path?
* [ ] **Failure isolation:** Can the team monitor, isolate, and recover from rendering, upstream data-access, cache, and client-activation failures independently?

## Related concepts [#related-concepts]

This decision connects directly to **Hydration**, **Frontend Data Fetching**, **HTTP Caching**, **CDN Behavior**, and the browser rendering/runtime model. Use the [Modern Web Systems](/docs/learning-paths/modern-web-systems) path to place these trade-offs in the larger request lifecycle.

## Sources [#sources]

* [Rendering on the Web — web.dev](https://web.dev/articles/rendering-on-the-web)
* [hydrateRoot — React](https://react.dev/reference/react-dom/client/hydrateRoot)
* [HTTP caching — MDN](https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/Caching)
* [Static and Dynamic Rendering — Next.js Learn](https://nextjs.org/learn/dashboard-app/static-and-dynamic-rendering)
