Lesson 02
Rendering: where does HTML get built?
CSR, SSR, SSG, ISR, Streaming. Five flavors of the same question — “who builds the page, and when?”
1 · The 5 modes, in plain English
🧠 in plain english
Imagine ordering a custom T-shirt. 5 ways to get it printed:
• CSR = you get a blank shirt, an iron-on, and instructions. You print it yourself at home. (Fast factory, slow at home.)
• SSR = factory prints one fresh shirt per order, then ships. (Slow factory, fast wear.)
• SSG = factory printed 1000 identical shirts last week, picks one off the shelf. (Instant ship, no personalization.)
• ISR = SSG, but factory reprints stock every week. (Cheap and fresh.)
• Streaming SSR = factory ships the box now with the shirt body, sleeves arrive 10 seconds later in a separate box. (See content immediately, full page assembles in pieces.)
• CSR = you get a blank shirt, an iron-on, and instructions. You print it yourself at home. (Fast factory, slow at home.)
• SSR = factory prints one fresh shirt per order, then ships. (Slow factory, fast wear.)
• SSG = factory printed 1000 identical shirts last week, picks one off the shelf. (Instant ship, no personalization.)
• ISR = SSG, but factory reprints stock every week. (Cheap and fresh.)
• Streaming SSR = factory ships the box now with the shirt body, sleeves arrive 10 seconds later in a separate box. (See content immediately, full page assembles in pieces.)
| Mode | HTML built | Best for | Tradeoff |
|---|---|---|---|
| CSR | in browser, after JS loads | auth dashboards | blank screen, bad SEO |
| SSR | per request, on server | personalized + SEO | TTFB blocks on slow query |
| SSG | at build time, once | marketing pages | stale until rebuild |
| ISR | at build, regen on schedule | blogs, products | first user post-expiry pays cost |
| Streaming | chunks via Suspense | data-heavy dashboards | caching is harder |
2 · Race them — see the difference
Same hypothetical page rendered three ways. Watch when each paints (user sees something) vs when it becomes interactive (clicks work).
CSR
SSR
Streaming
- CSR: blank screen until JS loads, then renders. SEO bad.
- SSR: server holds response until ALL data ready, then flushes everything. Bad if any query is slow.
- Streaming: server flushes shell immediately, slow data fills via Suspense. Best of both worlds.
Streaming wins for dashboards. User sees the header in 200ms while the wallet balance is still loading from the database. Old SSR would block the whole page on the slowest query.
3 · React Server Components in 60 seconds
🧠 in plain english
Imagine a Pixar movie. The backgrounds are pre-rendered (server). The characters need to move on screen (client). RSC = let the server pre-render the static parts and ship zero JavaScript for them. Only the interactive bits ship JS.
- RSC components render only on server. They never reach the browser as JavaScript.
- They can
awaita database call directly. No useEffect. - They can't use
useState,useEffect, event handlers, or browser APIs. "use client"is a boundary. Everything ABOVE = server. Everything BELOW = client.
Step through it line-by-line. Watch what runs on the server (left), what HTML streams to the browser (middle), and how the JS bundle stays at 0 KB until it hits a "use client" boundary:
React Server Components — what "use client" really does
step 0 / 10tsx
1// app/dashboard/page.tsx — SERVER component (default)
2export default async function Dashboard() {
3 const balance = await db.wallet.find(); // ← runs on server only
4 return (
5 <div>
6 <h1>Wallet</h1>
7 <Balance value={balance} /> {/* server, 0 KB */}
8 <PlaceBetButton /> {/* CLIENT, 12 KB */}
9 </div>
10 );
11}
12
13// app/PlaceBetButton.tsx
14"use client"; // ← boundary: this and below ship JS to browser
15export function PlaceBetButton() {
16 const [pending, setPending] = useState(false);
17 return <button onClick={...}>Bet</button>;
18}
🖥️SERVER
💻CLIENT
no JS yet
HTML payload streamed to browser
empty
JS bundle shipped
0KB
RSC zone
press ▶ play to step through this code, or use ← → to scrub.
4 · Hydration — visible ≠ interactive
If you remember nothing else from this lesson, remember this. SSR is fast at painting pixels but slow at making them interactive. Watch the timeline — paint at 400ms, but the button doesn't actually work until ~1450ms because the JS bundle has to download, parse, and reconcile.
Hydration — why visible ≠ interactive
step 0 / 9html
1// 1. Server renders HTML
2<html>
3 <body>
4 <h1>Wallet</h1>
5 <button>Place bet</button> <!-- not yet clickable -->
6 <script src="/bundle.js" defer></script>
7 </body>
8</html>
9
10// 2. Browser parses HTML, paints pixels (LCP fires)
11// 3. Browser downloads /bundle.js (~140 KB gzipped)
12// 4. JS executes — React.hydrateRoot(...)
13// walks the DOM tree, attaches event listeners, reconciles
14// 5. NOW the button works
⌛ blank screen — waiting on server
paint
—
JS bundle
0/140 KB
interactive
…
timeline
still loading
press ▶ play to step through this code, or use ← → to scrub.
📣 say this in the interview
“We use streaming SSR with Suspense around expensive data, so the navigation and static content paint immediately. Wallet balance is its own boundary that resolves independently. TTFB stays low even when one query is slow.”
⚡ quick check
A regulator demands that every page they audit returns its content in raw HTML, no JavaScript required. Which mode CAN'T satisfy this?