React hydration fails when the HTML the server generated does not match what the client renders on its first pass. The error means React cannot safely attach to the page it was given, because the markup diverged somewhere. Find and fix that divergence and the error disappears.
Why hydration happens
Hydration attaches React to HTML that the server already rendered. React walks the existing DOM and matches each element to the component tree, wiring up event handlers and state without rebuilding the page.
If the text or an attribute differs, React reports "Hydration failed because the server rendered HTML didn't match the client." The mismatch is the bug, not a symptom to ignore. React treats mismatches as problems to fix, and there is no guarantee it will patch attribute differences.
The common causes
Most hydration failures come from a handful of render-time decisions that produce different output on the server and in the browser.
| Cause | Why it diverges |
|---|---|
| Extra whitespace around the root | the raw HTML differs from the component output |
| typeof window checks in render | the server and client take different branches |
| Browser-only APIs in render | window.matchMedia exists only in the browser |
| Different data on each side | timestamps and random values differ per run |
Match the server render
A component that reads the clock during render produces a different string on every run, so the server and client never agree.
export default function Header() {
return <h1>Last updated: {new Date().toISOString()}</h1>;
}The server renders one timestamp into the HTML, then the client renders a new one, and hydration fails. Render a stable value first and update it only after the page is interactive.
import { useEffect, useState } from "react";
export default function Header() {
const [now, setNow] = useState("");
useEffect(() => {
setNow(new Date().toISOString());
}, []);
return <h1>Last updated: {now || "just now"}</h1>;
}Both server and client render "just now" on the first pass, so hydration succeeds. The Effect fills in the real time after mount.
Two-pass rendering for browser-only UI
When part of the UI only exists in the browser, render a placeholder first and switch after hydration.
import { useEffect, useState } from "react";
export default function MediaStatus() {
const [isClient, setIsClient] = useState(false);
useEffect(() => {
setIsClient(true);
}, []);
return <p>{isClient ? "Online" : "Checking..."}</p>;
}The first render matches the server output, so hydration passes. After hydration the component renders again with the browser-only branch. This renders twice, which costs a little time on slow connections.
Suppress only what you cannot fix
For a value that is legitimately different, such as a timestamp, add suppressHydrationWarning to that element. It silences the warning one level deep and is an escape hatch, not a fix.
Do not sprinkle it across the page to make warnings go away. The mismatch still exists underneath, and React may attach handlers to the wrong elements.
Verify the fix
Reload the page with the console open. The warning should be gone, and the first visible frame should match what the server sent. For a systematic walk through the failure, see how to debug React applications systematically.
Hydration is the bridge between server rendering and an interactive app. See how streaming SSR works with React Suspense for the server side of that bridge, where boundaries stream content in after the shell.
Rune AI
Key Insights
- Hydration fails when server and client markup diverge.
- Keep browser-only code out of the render path.
- Render the same data on the server and client.
- Use two-pass rendering for intentional client-only UI.
- suppressHydrationWarning is a one-level escape hatch.
Frequently Asked Questions
What does the hydration failed error mean?
Is suppressHydrationWarning a real fix?
Conclusion
Hydration fails when server and client render different markup. Match the two renders, move browser-only work out of render, and use two-pass rendering or suppression only for genuine differences.
More in this topic
How to Build a Dropdown Menu in React
Build a React dropdown menu with the ARIA menu button pattern. Handle open and close, keyboard arrows, and clicks outside the menu.
How to Animate React Components with Motion
Animate React components with the Motion library. Set up motion, add enter, hover, and exit animations, and respect reduced motion.
Headless UI Components Explained: Logic Without Locked Styling
Understand headless UI components and how libraries like Radix give you unstyled, accessible behavior that you style yourself.