What is hydration in React? It is the step where React, running in the browser, takes over HTML the server already rendered. It renders the same components again, matches the result to the existing DOM nodes and attaches event handlers, instead of building the page from scratch. The page is visible before JavaScript loads and interactive after hydration.
What is hydration in React, step by step
The steps of React hydration (SSR, then hydrateRoot) are:
- The server renders your components to HTML. A framework such as Next.js or React Router does this for you with
react-dom/server. - The browser shows that HTML. Text, images and links appear before any of your JavaScript has run. Buttons do nothing yet.
- The JavaScript bundle loads. It contains the same components the server rendered.
- React hydrates. It renders the component tree once more in the browser, walks the existing DOM alongside it, and attaches event handlers to the nodes it finds.
- The page is interactive. From here on, React updates the DOM like in any client-rendered app.
Step 4 only works if the browser's first render produces exactly the HTML the server sent. The hydrateRoot reference puts it plainly: the tree "needs to produce the same output as it did on the server."
React hydration explained in code
Frameworks call this for you, but underneath it is one function. With a custom server, the client entry looks like this:
import { hydrateRoot } from "react-dom/client";
import { App } from "./App";
// The server already rendered <App /> into #root.
hydrateRoot(document.getElementById("root")!, <App />, {
// Called when React recovers from an error, including a hydration mismatch.
onRecoverableError(error, errorInfo) {
console.error(error, errorInfo.componentStack);
},
});hydrateRoot, React's client API for server HTML, takes the DOM node, the same React element the server rendered, and options. The options include onRecoverableError, onCaughtError, onUncaughtError and identifierPrefix (the prefix for useId, which must match the one used on the server). Tutorials that write ReactDOM.hydrateRoot mean the same function, imported from react-dom/client.
hydrateRoot vs createRoot
hydrateRoot | createRoot | |
|---|---|---|
| Starts from | HTML the server rendered | An empty DOM node |
| First render | Adopts existing DOM nodes | Creates every DOM node |
| Needs | The same output on server and client | Nothing from the server |
| When the output differs | A hydration error | Not applicable |
Use createRoot for an app that is rendered only in the browser. React's docs say calling hydrateRoot without server HTML is not supported. hydration-proof reports a page that mounts with createRoot as HP9003, for information: nothing on that page is hydrated.
Hydration mismatch: meaning and consequences
A hydration mismatch means the first client render differs from the server HTML: different text, a different attribute, or a different element. React treats it as a bug. Its docs say: "You should treat mismatches as bugs and fix them."
What happens next depends on the React version and the build:
- Text or structure differs (React 19): React throws the server HTML away and renders that part of the tree again on the client. The whole root is rendered again if no Suspense boundary contains the mismatch. In development it logs a diff; in production, a minified error #418.
- An attribute differs (React 19): React keeps the server's value and does not patch it. Development builds warn; production builds report nothing at all.
- React 18: the same kinds of differences produce "Hydration failed because the initial UI does not match what was rendered on the server" and related warnings, or minified errors #418, #423 and #425 in production.
The React hydration error guide lists every message and cause, and all error messages decodes them one by one.
What is hydration in web development?
Outside React, the word means the same thing. A server renders a page to HTML, and a client-side framework later takes over that HTML by attaching its state and event listeners rather than replacing the markup. The alternative is client-side rendering, where the server sends an empty shell and JavaScript builds the whole page.
Hydration gives you HTML for the first paint, for crawlers and for users on slow connections, plus a full client app afterwards. The cost is that the component code runs twice, once on each side, and both runs must agree. Astro applies the idea per component: each React island hydrates on its own, when its client:* directive says so.
What is hydration in Next.js?
Next.js server-renders your pages and calls hydrateRoot for you. How much of the page hydrates depends on the router:
- Pages Router: the whole page is a client component tree. It is rendered on the server and hydrated in the browser.
- App Router: Server Components render only on the server. Client Components, the files marked
"use client", are also prerendered to HTML on the server and then hydrated."use client"does not mean "client only".
That last point causes many mismatches: code in a Client Component that reads window, localStorage or the clock runs on the server too. See Next.js hydration errors for the common cases.
Do Server Components hydrate?
No. With React Server Components, hydration covers only the Client Components. A Server Component's code never runs in the browser, so reading the clock or a random value there cannot cause a mismatch: the browser receives the result, not the code.
The HTML elements a Server Component renders are still part of the page React hydrates. Invalid nesting, such as a <div> inside a <p>, breaks hydration whether a Server Component or a Client Component wrote it.
Streaming and selective hydration
With streaming server rendering, the server sends HTML in pieces: the shell first, then each Suspense boundary when its data is ready. React hydrates the root first and then each boundary once its HTML and code have arrived, and it hydrates the boundary the user interacts with first.
Boundaries also limit the damage of a mismatch. When hydration fails inside a Suspense boundary, React renders only that boundary again on the client. hydration-proof reports that as HP1010 and a whole-page re-render as HP1011. How hydration-proof works explains how it checks each streamed boundary on its own.
Check that your pages hydrate
Reading the console on one page at a time misses most mismatches. hydration-proof loads every route in a real browser and compares the server HTML with what React renders:
npm install -D hydration-proof
npx hydration-proof install
npx hydration-proof testThe quick start walks through the first report.