- React
- Server-Side Rendering, Hydration, and Server Components
- Server-Side Rendering (SSR) & Client Hydration Mechanics
Server-Side Rendering (SSR) & Client Hydration Mechanics
Server-Side Rendering renders the initial React component tree to static HTML on the server for fast First Contentful Paint. Client hydration walks the existing server-rendered DOM tree, creates internal Fiber nodes, and attaches synthetic event listeners without recreating DOM elements.
Key Learning Objectives
Trace the end-to-end SSR request lifecycle: renderToString/renderToPipeableStream -> HTML transfer -> hydrateRoot.
Explain what hydration actually does: fiber construction, attribute reconciliation, and event listener binding.
Explain why recreating DOM nodes during hydration degrades Time to Interactive (TTI).
Compare client-side rendering (CSR) and server-side rendering (SSR) performance metrics (FCP, LCP, TTI).
The Interview Problem & Context
Describe what happens during the client hydration phase in React, and explain why React expects the server-rendered DOM and client-rendered tree to match identically.
1// 1. SERVER EXECUTION (Node.js / Edge Runtime)2import { renderToString } from 'react-dom/server';3import App from './App';45export function handleServerRequest(req: Request) {6 // Renders component tree purely to an HTML string7 // Note: useEffect, event listeners, and browser APIs DO NOT RUN here!8 const htmlMarkup = renderToString(<App />);910 const html = '<!DOCTYPE html><html><body><div id="root">' + htmlMarkup + '</div><script src="/bundle.js" defer></script></body></html>';11 return new Response(html, { headers: { 'Content-Type': 'text/html' } });12}1314// 2. CLIENT EXECUTION (Browser Runtime)15import { hydrateRoot } from 'react-dom/client';16import App from './App';1718const container = document.getElementById('root')!;1920// hydrateRoot expects existing DOM nodes inside #root!21// It reconciles Fiber nodes against the live DOM and attaches event listeners.22const root = hydrateRoot(container, <App />);
Deep Architectural Explanation
The Lifecycle of Server-Side Rendering & Hydration
Phase 1: Server Generation
- The server executes renderToString(<App />).
- React runs component render functions synchronously to generate an HTML string.
- No lifecycle side-effects run: useEffect, useLayoutEffect, and DOM event listeners (onClick) are completely skipped on the server.
- The browser receives plain HTML, parses it, and paints the initial screen immediately (Fast First Contentful Paint - FCP).
Phase 2: Client Hydration
- The browser downloads and executes bundle.js.
- hydrateRoot(container, <App />) is invoked.
- React renders the component tree in memory, producing a draft Fiber tree.
- Rather than calling document.createElement, React walks the existing live DOM nodes:
- Matches each Fiber to its corresponding DOM element.
- Binds synthetic event listeners (e.g. attaching click listeners to buttons).
- Once complete, the page is fully interactive (Time to Interactive - TTI).
Common Interview Traps & Pitfalls
Assuming useEffect runs on the server during SSR.
Calling browser-only APIs like window.localStorage directly in component render bodies during SSR.
Using createRoot instead of hydrateRoot on server-rendered HTML, causing React to blow away existing DOM nodes.
Senior Interviewer Follow-Up Probes
How does React 18 renderToPipeableStream improve upon renderToString for Node.js backends?
What is Progressive Hydration and how does it relate to React 18 Suspense streaming?
How does Selective Hydration prioritize hydrating a component that a user just clicked on?
Frequently Asked Interview Questions
What is the difference between createRoot and hydrateRoot?
createRoot clears the container DOM and mounts a brand-new component tree from scratch (Client-Side Rendering). hydrateRoot assumes the container already contains matching server-rendered DOM and only attaches React internals and listeners.
Why does SSR improve SEO and Core Web Vitals?
Search engine crawlers and users receive complete HTML immediately on the initial HTTP response, drastically improving First Contentful Paint (FCP) and Largest Contentful Paint (LCP) compared to blank CSR shells.
Do useEffect and useLayoutEffect run on the server during SSR?
No. Effects only execute on the client after hydration is complete and DOM nodes are active in the browser.
What happens if a user clicks a button before hydration completes?
The click event fires on the native DOM, but because React has not yet attached its event listeners to that element, nothing happens unless an early event recorder script captures it.
What is the downside of traditional monolithic SSR?
The "all-or-nothing" bottleneck: the server cannot send any HTML until all data fetches complete, and the client cannot interact with any part of the page until the entire JavaScript bundle downloads and hydrates.
