CSR, SSR, SSG, and Hybrid Rendering Strategies

Compare Client-Side Rendering, Server-Side Rendering, Static Site Generation, and Incremental Static Regeneration, and understand how streaming SSR and selective hydration let a framework mix these strategies within a single app or even a single page.

There is no single 'correct' way to turn a React component tree into pixels on a screen — where the rendering work happens (build time, request time, or in the browser) is a deliberate trade-off between time-to-first-byte, interactivity, freshness of data, and infrastructure cost. Modern React frameworks let you pick a strategy per page, or even mix several within one response using streaming, rather than forcing a single all-or-nothing choice for the whole app.

The three classic strategies: CSR, SSR, SSG

Client-Side Rendering (CSR) ships a near-empty HTML shell and a JavaScript bundle; the browser executes that bundle to build the entire page. This is the default (and only) mode for a plain Vite + React app. It's simple and cheap to host (any static file server works), but the user waits through a blank/loading state until JS has downloaded and run, and content isn't in the initial HTML for crawlers that don't execute JavaScript.

Server-Side Rendering (SSR) runs your component tree on a server for every request, producing full HTML immediately, then hydrates it in the browser. The user sees real content immediately (fast first paint), and the HTML is crawlable, but each request costs server compute time, and the page still isn't interactive until hydration finishes.

Static Site Generation (SSG) runs your component tree once, at build time, producing HTML files that get uploaded to a CDN. Every visitor gets the same pre-built HTML served instantly from edge servers close to them — no per-request server compute at all, and typically the fastest possible time-to-first-byte. The catch is that the content is frozen at build time; if the underlying data changes, the page is stale until you rebuild and redeploy.

Incremental Static Regeneration (ISR): the SSG freshness fix

Incremental Static Regeneration is a hybrid idea (popularized by Next.js) that keeps SSG's 'serve pre-built HTML from a CDN' speed while solving its staleness problem. A page is generated statically like normal SSG, but marked with a revalidation window (for example, 'this page can be at most 60 seconds stale'). Visitors keep getting the fast, cached static HTML, and in the background — the next time a request comes in after that window expires — the server regenerates the page and swaps the cached version for the new one.

This means you get near-static performance with data that's never more than a bounded amount of time out of date, without needing to trigger a full site rebuild every time the underlying data changes. ISR is a framework-level feature layered on top of the concepts of SSG and SSR — it isn't part of React itself, and different frameworks implement their own versions of the idea (or don't offer it at all).

Streaming SSR and selective hydration

Traditional SSR has an all-or-nothing quality: the server has to finish rendering the *entire* page to HTML before it can send anything, and the client can't hydrate anything until the whole HTML document and the whole JS bundle have arrived. If one part of the page is slow (a slow data fetch deep in the tree), it holds up the whole response.

Streaming SSR, built around <Suspense> boundaries, changes this: the server sends the HTML for the fast parts of the page immediately, and streams in the HTML for slower parts as their data becomes ready, each wrapped in its own <Suspense fallback> that gets replaced in place once the real content arrives — all within a single HTTP response that stays open and flushes chunks over time.

Selective hydration pairs with this: instead of waiting for the entire page's JavaScript to load and hydrating everything at once, React can hydrate individual Suspense-wrapped sections as their code and data become ready, and it can prioritize hydrating whichever part the user is actually interacting with (for example, if the user clicks a button in a part of the page that hasn't hydrated yet, React can jump ahead and hydrate that part first). The practical effect is that a page with one slow section no longer blocks fast interactivity everywhere else.

Hybrid rendering and how Server Components fit in

'Hybrid rendering' just means: not every page in an app uses the same strategy, and sometimes not even every part of one page does. A marketing site's blog posts might be SSG (content rarely changes, maximize speed), its dashboard might be SSR (data must be fresh per-request), and small widgets embedded in either might still do plain CSR for their own client-only data. Frameworks like Next.js let you choose per-route, and even mix static and dynamic pieces within one route using streaming.

React Server Components add another axis to this: RSC is really about *what code runs where and ships where* (server-only vs. client-bundled code, covered in depth in the previous lesson), while CSR/SSR/SSG/ISR are about *when* the rendering work happens (build time, request time, or in-browser). In practice, frameworks combine them: a Server Component's output can be rendered ahead of time (SSG-like) or per-request (SSR-like) or streamed in progressively, while Client Components nested inside it still hydrate in the browser the normal way. The two ideas are related but answer different questions.

A streaming SSR boundary with Suspense

The <h1> and page shell are sent to the browser as soon as they're ready, without waiting on SlowRecommendations. The framework's streaming server renderer replaces the fallback with the real markup in the same HTTP response once fetchRecommendations() resolves. This pattern requires a framework/server capable of streaming SSR — it is not something a plain Vite CSR build does.

import { Suspense } from "react";

async function SlowRecommendations() {
  const items = await fetchRecommendations(); // slow: e.g. 2s
  return (
    <ul>
      {items.map((item) => (
        <li key={item.id}>{item.name}</li>
      ))}
    </ul>
  );
}

export default function ProductPage() {
  return (
    <main>
      <h1>Product details</h1> {/* sent immediately */}
      <Suspense fallback={<p>Loading recommendations...</p>}>
        <SlowRecommendations /> {/* streamed in once ready */}
      </Suspense>
    </main>
  );
}

Code Examples

The blog post's content changes rarely, so pre-rendering it once at build time (SSG) gives the fastest possible delivery via a CDN. The dashboard's data changes constantly, so it's marked to render fresh on every request (SSR) instead. The exact configuration API shown here is illustrative of the general pattern that real frameworks expose, not literal React syntax.

// A blog post: content rarely changes -> SSG is ideal
export async function generateStaticParams() {
  const posts = await getAllPostSlugs();
  return posts.map((slug) => ({ slug })); // pre-rendered at build time
}

export default async function BlogPost({ params }: { params: { slug: string } }) {
  const post = await getPostBySlug(params.slug); // runs once, at build time
  return <article>{post.body}</article>;
}

// A user dashboard: data must be fresh per-request -> SSR
export const dynamic = "force-dynamic"; // opt this route into per-request SSR

export default async function Dashboard() {
  const liveStats = await getLiveStats(); // runs on every request
  return <section>Active now: {liveStats.activeUsers}</section>;
}

CSR vs. SSR vs. SSG

CSR does all rendering work in the browser at visit time. SSR renders on the server for every request, then hydrates. SSG renders once at build time and serves the same static HTML to every visitor from a CDN.

CSR: Browser → JavaScript → Render

SSR: Server → HTML → Browser → Hydration

SSG: Build → HTML → CDN → Browser

Important Rules

CSR: all rendering happens in the browser after the JS bundle loads — cheapest to host, slowest first paint, this is what plain Vite + React gives you by default.

SSR: HTML is rendered fresh on the server for every request, then hydrated in the browser — fast first paint, always fresh data, but costs server compute per request.

SSG: HTML is rendered once at build time and served statically from a CDN — fastest possible delivery, but content is frozen until the next build.

ISR keeps SSG's CDN-speed delivery while bounding staleness by regenerating pages in the background after a configured revalidation window.

Streaming SSR sends HTML in chunks as it becomes ready (via Suspense boundaries) instead of waiting for the entire page to finish rendering on the server.

Selective hydration lets React hydrate and prioritize individual Suspense-wrapped sections independently, instead of hydrating the whole page as one blocking unit.

Where this shows up

An e-commerce site is a classic hybrid case: product catalog pages are often SSG or ISR (content changes occasionally, huge traffic benefits from CDN caching), the checkout and cart pages need SSR or CSR for per-user, per-request freshness, and small widgets like 'items in your cart' badge might be plain client-rendered against a client-side API call. Frameworks like Next.js, Remix, and Astro each expose their own take on choosing (and mixing) these strategies per route.

Using full SSR (server render on every request) for a marketing page whose content changes maybe once a month.

Use SSG (or ISR with a long revalidation window) for content that rarely changes, reserving per-request SSR for genuinely dynamic, user-specific, or real-time data.

Rendering static content fresh on every single request wastes server compute and adds latency for no benefit — a CDN-served static file would be faster and cheaper for the exact same result.

Assuming ISR or streaming SSR are core React features available in any React app, including a plain Vite CSR build.

Recognize these as framework-level (and often framework-specific) capabilities layered on top of React's rendering primitives, requiring a server and build tooling that plain client-side Vite does not provide.

React itself provides the underlying building blocks (like Suspense and the streaming renderer APIs), but assembling them into 'ISR' or a per-route rendering strategy is done by a meta-framework, not by React alone.

[multiple-choice] What is the key difference between SSR and SSG? A. SSR only works with class components; SSG only works with function components B. SSR renders HTML fresh on every request; SSG renders HTML once at build time and serves the same static output to everyone C. SSG requires JavaScript to be disabled in the browser D. There is no real difference — they're two names for the same thing

B. SSR renders HTML fresh on every request; SSG renders HTML once at build time and serves the same static output to everyone — SSR performs a render pass on the server for each incoming request, producing potentially different HTML each time. SSG performs that render pass once, at build time, producing static HTML files that are then served unchanged (from a CDN) to every visitor until the next build.

[multiple-choice] What problem does Incremental Static Regeneration (ISR) solve? A. It makes CSR pages render faster in the browser B. It lets statically generated pages stay reasonably fresh by regenerating them in the background after a revalidation window, without a full site rebuild C. It removes the need for hydration entirely D. It converts Client Components into Server Components automatically

B. It lets statically generated pages stay reasonably fresh by regenerating them in the background after a revalidation window, without a full site rebuild — ISR keeps the speed of serving pre-built static HTML from a CDN while addressing SSG's staleness problem: pages are regenerated on the server after a configured time window, and the cached static output is swapped in, without requiring a full rebuild/redeploy of the whole site.

[true-false] With streaming SSR and selective hydration, a page must fully hydrate before any part of it can respond to user interaction. A. True B. False

B. False — False. Selective hydration allows React to hydrate individual Suspense-wrapped sections independently, and it can prioritize hydrating the section the user is actually interacting with, rather than requiring the entire page to hydrate as one blocking unit before anything responds.

For each of the following pages, decide which strategy (CSR, SSR, SSG, or ISR) fits best, and briefly say why: (1) a public documentation page that's updated a few times a year, (2) a live stock-ticker dashboard, (3) a news homepage that should reflect new articles within about a minute of publishing.

(1) SSG — content changes rarely, so build-time rendering and CDN delivery gives the best speed with no downside. (2) SSR (or even CSR with client-side polling/websockets) — the data must be current at the moment of viewing, so pre-rendering doesn't make sense. (3) ISR with a short revalidation window (e.g. 60 seconds) — gets SSG's CDN speed while staying acceptably fresh without a full rebuild per article.

Think about how often the data changes and how important per-request freshness really is versus raw delivery speed.