Learn the concept
React Internals
Hydration is the process where React attaches event handlers and state to server-rendered HTML, making it interactive without re-rendering the DOM. Mismatches occur when server and client HTML differ, causing React to discard and re-render the affected subtree.
When using Server-Side Rendering (SSR), the server generates HTML and sends it to the browser. The browser displays this HTML immediately (fast First Contentful Paint). Then React hydrates the page — it walks the existing DOM, attaches event handlers, and connects state management without recreating DOM nodes.
renderToString() or renderToPipeableStream()hydrateRoot() (React 18+) instead of createRoot()A hydration mismatch occurs when the HTML rendered on the server differs from what React expects on the client. React logs a warning and may discard the mismatched subtree, re-rendering it from scratch (losing the SSR performance benefit).
Common causes:
Browser-only APIs: Using window, document, localStorage, or navigator during render — these don't exist on the server.
Non-deterministic values: Date.now(), Math.random(), or crypto.randomUUID() produce different values on server vs client.
Browser extensions: Extensions that modify the DOM (ad blockers, password managers, translation tools) add elements React doesn't expect.
Invalid HTML nesting: <p><div>...</div></p> — the browser auto-corrects invalid nesting, creating a DOM that differs from what React generated.
Conditional rendering based on client state: Rendering different content based on window.innerWidth or media queries without proper handling.
useEffect runs only on the client, after hydration.next/dynamic: Load client-only components with ssr: false.suppressHydrationWarning: Silence warnings for intentional differences (e.g., timestamps). Use sparingly.React 18 introduced streaming SSR with renderToPipeableStream(). Instead of waiting for the entire page to render, the server streams HTML as components resolve:
<script> that swaps the fallbackReact 18 also enables selective hydration: React prioritizes hydrating the component the user is interacting with. If a user clicks a button inside a Suspense boundary that hasn't hydrated yet, React hydrates that boundary first, even if other boundaries are ahead in the queue.
SSR renders components to HTML on the server, then hydrates them on the client (the component code ships to the browser). React Server Components (RSC) render on the server and never ship their JavaScript to the client — they send a serialized component tree instead. RSC and SSR are complementary: RSC reduces bundle size, SSR provides fast initial paint.
// BAD — causes hydration mismatch
function Greeting() {
// window.innerWidth doesn't exist on server
const isMobile = window.innerWidth < 768; // ❌ Crashes on server
return <p>{isMobile ? 'Mobile' : 'Desktop'} view</p>;
}
// GOOD — two-pass rendering
function Greeting() {
const [isMobile, setIsMobile] = useState(false);
useEffect(() => {
// Runs only on client, after hydration
setIsMobile(window.innerWidth < 768);
const handleResize = () => setIsMobile(window.innerWidth < 768);
window.addEventListener('resize', handleResize);
return () => window.removeEventListener('resize', handleResize);
}, []);
return <p>{isMobile ? 'Mobile' : 'Desktop'} view</p>;
}
// GOOD — suppress warning for timestamps
function Comment({ timestamp }) {
return (
<time suppressHydrationWarning>
{new Date(timestamp).toLocaleString()}
</time>
);
}Product pages use streaming SSR to show the product title and image immediately while reviews and recommendations load progressively via Suspense boundaries.
Article pages hydrate the main content first for fast readability, while interactive widgets (comments, share buttons) hydrate lazily.
Build a development tool that detects hydration mismatches in a Next.js app by comparing server-rendered HTML with client-rendered output, highlighting differences.