Definition
A hydration mismatch is the situation where the HTML the server rendered differs from the first render result on the client, so the framework fails to reconcile the tree while reattaching (hydrating) event handlers onto the existing DOM. It typically occurs when the first render uses values the server doesn't have (localStorage, window, random, the current time).
Why it matters
SSR (server-side rendering: the server builds and sends HTML on each request) / SSG (static site generation: HTML is prebuilt at build time) send HTML built on the server first so the user sees content quickly (critical-rendering-path), and in the browser React reattaches (hydrates) the same tree to add interactivity. When this "server markup == first client render" premise breaks, a warning fires, and flicker or DOM discard · re-creation occurs, degrading performance and visual stability. So the key is when you apply client-only state.
How it works
The server has no window · localStorage. So when a component reads such values on its first render, the server renders with a default and the client with the stored value, and the two diverge. During hydrate React compares the two trees, finds the difference, and warns.
| Strategy | Method | Tradeoff |
|---|---|---|
| apply once after mount | first render matches SSR's default; apply the client value in useEffect | one-frame flicker possible |
| synchronous inline script | an inline <script> sets the DOM attribute directly before hydrate | no flicker, more boilerplate |
| suppress intended mismatch | suppressHydrationWarning only for inherently different values like time | must not mask real bugs |
Practical application
If you must eliminate even the flicker, set the DOM attribute directly with an inline script that runs before hydrate, then let React reattach on top of it. The default rule is to read persistent layout values (localStorage, etc.) after mount, not at store-initialization time.
Tradeoffs
- Applying after mount is simple to implement but can show a one-frame flicker on the default → actual-value transition.
- The inline script has no flicker but grows the code, and you must keep the script and React state from diverging.
When not to use
- CSR-only apps (with no server render, there's no mismatch to begin with).
- When the value can be determined on the server too — render it on the server instead of needlessly deferring it to client-only.
Common mistakes
- Directly using
localStorage/window/Date.now()/random in the first render body. - Slapping
suppressHydrationWarningbroadly, masking real mismatch bugs. - Initializing the store from
localStorage(a client value from the first render), diverging from the server.
Related concepts
- critical-rendering-path — the rendering order where server-built HTML is painted first and JS reattaches afterward.