Skip to solution
hardSystem Design

What is the difference between client-side rendering (CSR) and server-side rendering (SSR) in React?

241 views
01

Understand the problem

Question presented to candidate: "Walk me through what the browser receives with client-side rendering versus server-side rendering, and what that changes."

What a strong answer should cover:

  • CSR: the response is an almost-empty HTML shell — a <div id="root"> and a script tag. Nothing is visible until the bundle downloads, parses, executes, and renders.
  • SSR: the response already contains the finished markup, so the browser paints content on the first parse, before any JavaScript runs.
  • The difference is when content appears, not how it looks once loaded. Both end in exactly the same DOM.
  • Map it to metrics: SSR improves FCP/LCP and gives crawlers real content; TTI is not automatically better, because hydration still has to run.
  • CSR's costs land on the client (bundle download, parse, execute); SSR's land on the server (CPU per request) plus deployment complexity.
  • CSR is genuinely better for repeat in-app navigation — subsequent routes need data, not a whole document round trip.
  • The honest answer is that this is rarely an app-wide binary any more: frameworks mix static generation, per-request SSR, streaming, and client islands per route.

Clarifying questions expected:

  • "Is this a first visit or in-app navigation?" — the answer reverses between them.
  • "Public and crawlable, or behind a login?"

Code / implementation expected: No. This is a comparison question; a timeline and the metric names carry it.

ssrcsrperformanceseo
02

Attempt it yourself

Sketch your approach before reading the solution — that's what interviews test.

Nudge consolestandby

Stuck? Beam a request up — the console returns a conceptual nudge that guides your logic without spoiling the implementation.

03

Study the solution

Target Audience: Engineers preparing for React interviews — no prior rendering-strategy knowledge assumed. Difficulty: Hard

How to read this doc: Concepts are explained in plain language first, then tagged with 📌 Interview term:. The byte counts in section 3 come from **actually rendering t

Solution ready — 2 min read

Classified // press E to declassify

04

Explore the playground snippets

The two response bodies, side by side, for the same component
Run Playground
import { useState } from "react";
import { renderToString } from "react-dom/server";

const PRODUCTS = Array.from({ length: 8 }, (_, i) => "Product " + i);

function Catalogue() {
  return (
    <main>
      <h1>Catalogue</h1>
      <ul>{PRODUCTS.map((p) => <li key={p}>{p}</li>)}</ul>
    </main>
  );
}

// Both computed at module scope — never call a server renderer during a client
// render, which would nest one React render inside another.
const csrShell = '<div id="root"></div><script src="/bundle.js"></script>';
const ssrHtml = renderToString(<Catalogue />);

// What a crawler that does not execute JavaScript would read.
const textOf = (html) => html.replace(/<[^>]*>/g, " ").replace(/\s+/g, " ").trim();

export default function App() {
  const [showBytes, setShowBytes] = useState(true);

  return (
    <div style={{ padding: 24, fontFamily: "system-ui", lineHeight: 1.7, maxWidth: 600 }}>
      <label style={{ fontSize: 13 }}>
        <input type="checkbox" checked={showBytes} onChange={(e) => setShowBytes(e.target.checked)} />
        {" "}show byte counts
      </label>

      <Row
        title="❌ CSR — what the server sends"
        html={csrShell}
        text={textOf(csrShell)}
        showBytes={showBytes}
      />
      <Row
        title="✅ SSR — what the server sends"
        html={ssrHtml.slice(0, 160) + (ssrHtml.length > 160 ? " …" : "")}
        text={textOf(ssrHtml)}
        showBytes={showBytes}
        bytes={ssrHtml.length}
      />

      <p style={{ fontSize: 13, color: "#666" }}>
        The bottom line of each block is what something reading the response
        WITHOUT running JavaScript sees — a crawler, a link-preview bot, or a
        browser whose script request failed. Both pages end up identical once
        the bundle has run; only the first response differs.
      </p>
    </div>
  );
}

function Row({ title, html, text, showBytes, bytes }) {
  return (
    <div style={{ border: "1px solid #ddd", borderRadius: 8, padding: 12, margin: "12px 0" }}>
      <strong style={{ fontSize: 13 }}>{title}</strong>
      {showBytes && (
        <span style={{ fontSize: 12, color: "#666" }}> · {bytes ?? html.length} bytes</span>
      )}
      <pre style={{ background: "#f6f6f8", padding: 8, borderRadius: 6, fontSize: 11, overflowX: "auto", margin: "6px 0" }}>
{html}
      </pre>
      <div style={{ fontSize: 12, color: text ? "#161" : "#a33" }}>
        readable without JS: {text ? JSON.stringify(text.slice(0, 70)) : "(nothing)"}
      </div>
    </div>
  );
}
05

Join the discussion

Discussion (0)

Sign in to join the discussion.

No responses yet. Be the first to share what you think.

Transmission complete // awaiting log

KEEP THE
STREAK ALIVE.

Dossier 112 of 119 decoded in the React.js track. One more won't hurt.

Back to track