An intentional hydration mismatch is a difference you accept: a timestamp the
server renders and the browser replaces, or a theme class a script sets before
hydration. Mark the one element that differs with suppressHydrationWarning,
keep it on the smallest element possible, and let hydration-proof list what
each suppression hides.
Symptoms
Nothing in the console: that is what the attribute is for. hydration-proof still sees the difference and reports it, so a suppression cannot quietly cover a real bug:
| Code | Severity | Meaning |
|---|---|---|
| HP6001 | info | A difference hidden by suppressHydrationWarning, with both values |
| HP6002 | error | The attribute cannot hide this: the elements inside differ |
| HP6003 | info | Nothing on the element differs, so the attribute hides nothing today |
These findings get the cause Intentional difference (suppressHydrationWarning). When the hidden difference does not look intentional (not a time, timezone, locale, random, theme or extension difference), HP6001 is raised to a warning with the note "suppressHydrationWarning hides a difference that does not look intentional", and the fix for the real cause is shown first.
What suppressHydrationWarning does
suppressHydrationWarning tells React to keep the server's text and
attributes for one element without reporting that they differ. The React
documentation is direct about its limits: "This only works one level deep, and
is intended to be an escape hatch. Don't overuse it. React will not attempt
to patch mismatched text content."
So the element keeps showing the server's value until something re-renders it, and anything inside a child element is not covered at all.
Which intentional hydration mismatch is safe?
| Difference | Suppress it? |
|---|---|
| A live clock or "last updated" time that updates right after hydration | Yes, on the element that shows the time |
The theme class a pre-hydration script sets on <html> | Yes, on <html> only |
Attributes browser extensions add to <body> | Only if the development warning gets in your way |
| Data, ids, CSS-in-JS class names, invalid nesting | No: fix the cause |
When the difference should not exist, the cause pages have the fix: start with all causes of hydration errors.
How to fix it
Put it on the smallest element
The attribute covers the element's own text and attributes. On a wrapper, it misses the text inside a child:
// Before: still a hydration error, the text is in the <span>
export function ClockBefore({ now }: { now: Date }) {
return (
<div suppressHydrationWarning>
<span>{now.toLocaleTimeString("en-US", { timeZone: "UTC" })}</span>
</div>
);
}
// After: on the element whose text differs
export function Clock({ now }: { now: Date }) {
return (
<div>
<span suppressHydrationWarning>
{now.toLocaleTimeString("en-US", { timeZone: "UTC" })}
</span>
</div>
);
}It also does nothing on a component (<Clock suppressHydrationWarning />)
unless the component passes the prop to an HTML element.
Fix structural differences instead
suppressHydrationWarning never covers elements that are added, removed or
replaced. If the server renders a <p> and the client a <div> inside the
marked element, React still fails hydration, and hydration-proof reports
HP6002. Render the same elements on both sides and let only text or attributes
differ.
Remove it where nothing differs
HP6003 means the element's server and client output are identical. The attribute is doing nothing today and will hide the next real mismatch on that element. Remove it.
Prefer rendering after mount
Even an intended difference can often be avoided: render a placeholder on the server and the real value in an effect. The page then never shows a stale server value. See useEffect and two-pass rendering and when suppressHydrationWarning is safe.
Catch it with ESLint
audit-suppress-hydration-warning
reports suppressHydrationWarning on an element with element children (it
only covers one level), on an element whose content is static (nothing can
differ, with a suggestion to remove it) and on a component. <html> and
<body> are accepted by default. The strict preset reports every other use
too, so each suppression needs an eslint-disable comment that explains it.
The rule also checks Server Components, since root layouts are one.
npm install -D eslint-plugin-hydration-proofCatch it in CI
hydration-proof test lists every suppressed difference as HP6001 with the
server and client values, so a review can check that each one is the
intended timestamp or theme class and nothing else. HP6002 fails the run;
HP6001 and HP6003 are info unless the hidden difference looks like a bug.
npx hydration-proof testTo keep a difference out of hydration-proof's results without touching React,
use the ignore options or a data-hydration-proof-ignore attribute instead:
see ignoring findings.