Hydration Proof

Search documentation

Find a page or section

Common causes of hydration errors

One fix guide per root cause, linked from every finding.

The common causes of hydration errors have one thing in common: the first browser render does not match the server HTML. Find the changed value or element, then open the matching cause guide below for a fix.

All common causes of hydration errors

hydration-proof links each finding to a likely cause guide:

Cause idWhat it means
timeTime-dependent value
timezoneTimezone difference
localeLocale-dependent formatting
randomRandom value
browser-apiBrowser-only API used during render
storagelocalStorage or sessionStorage read during render
media-queryScreen size or media query read during render
themeTheme preference (dark or light mode)
dataServer and client used different data
invalid-htmlInvalid HTML nesting
css-in-jsCSS-in-JS class names differ
extensionBrowser extension
third-party-scriptA script changed the page before hydration
cdnHTML rewritten by a CDN or proxy
unstable-idGenerated id differs
form-stateServer Action form state
suppressedIntentional difference (suppressHydrationWarning)

What React lists as the causes

React 19 includes a list of possible causes in its error message:

Hydration failed because the server rendered HTML didn't match the client. As a result this tree will be regenerated on the client. This can happen if a SSR-ed Client Component used:
 
- A server/client branch `if (typeof window !== 'undefined')`.
- Variable input such as `Date.now()` or `Math.random()` which changes each time it's called.
- Date formatting in a user's locale which doesn't match the server.
- External changing data without sending a snapshot of it along with the HTML.
- Invalid HTML tag nesting.
 
It can also happen if the client has a browser extension installed which messes with the HTML before React loaded.
React's lineCause pages
A server/client branchBrowser APIs, storage, media queries, theme
Date.now() or Math.random()Time, random values, generated ids
Date formatting in a user's localeLocale, timezone
External changing dataData, form state
Invalid HTML tag nestingInvalid HTML
A browser extensionExtensions, third-party scripts, CDNs

React's list is not exhaustive. CSS-in-JS class names and suppressed differences can also matter.

How hydration-proof picks a cause

The report suggests a cause using four kinds of evidence:

  1. The two values. Millisecond timestamps, the same number with different separators, random decimals and UUIDs, class names that look generated (sc-, css-, emotion-), theme classes (dark, light), React ids and whitespace-only differences each point at one cause.
  2. The code near the line. hydration-proof maps the element to your source file and scans the component that rendered it for Date.now(), localStorage, matchMedia, fetch, typeof window and similar calls. Code in a neighboring component never explains a finding.
  3. The scenario. A browser timezone or locale that differs from the server's, dark mode, a mobile viewport or pre-filled storage.
  4. The stage. Markup the browser's parser repaired is invalid nesting. A change between parsing and hydration came from a script or an extension. A form the server rendered with Server Action state is form state. A difference on an element with suppressHydrationWarning is intentional.

The report shows its confidence and the supporting reason. When confidence is too low, it does not name a cause. A browser branch such as typeof window is easier to identify in a development build with source locations. See how it works.

Prove the cause with probes

Use --probe to test a suggested cause. It reloads the page while changing one browser factor at a time:

FactorWhat changes
timeThe browser clock moves by 3 days, 7 hours, 11 minutes and 13 seconds
randomA different seed for Math.random() and crypto.getRandomValues()
localeThe server's locale, or another one
timezoneThe server's timezone, or another one
themeLight and dark swap
viewportDesktop and mobile swap
storageThe page loads without the scenario's localStorage, sessionStorage and storageState

If a single change moves or removes the finding, the report marks that factor as proven. If identical reloads differ, inspect server or API data instead. See probes for the details.

npx hydration-proof test --probe

Causes, ESLint rules and issue codes

The ESLint plugin catches many risky patterns in source code. The browser check finds differences that a linter cannot see. Each cause guide links to its relevant rules. The confidence values below come from the package's production-build fixtures; they are not a promise for every app.

CauseCommon findingsFixture confidence
TimeHP1001, HP100297%
TimezoneHP100195%
LocaleHP100198%
Random valuesHP1001, HP100294%
Browser APIsHP1001, HP1002, HP100357–59%
StorageHP100194%
Media queriesHP1001, HP100489%
ThemeHP1004, HP100299%
Changing dataHP1001, HP1007–HP100968%
Invalid HTMLHP3001, HP300297%
CSS-in-JSHP100490%
ExtensionsHP4002, HP4001Not measured
Third-party scriptsHP400180%
CDN changesHP1015, HP400388%
Generated idsHP1002, HP3003, HP3004Not measured
Form stateHP1012Not measured
Suppressed differencesHP6001–HP6003Not measured

browser-api scores lower because the values alone never show it: the cause comes from the code, which is exact in development builds and depends on source maps in production.