Text content does not match server-rendered HTML, explained and fixed

During hydration, React compares the HTML the server sent with the first render in the browser, and they must agree. This warning (an error in development) means some text differed between the two: the server rendered one string, the client's first render produced another. React then has to discard and re-render the mismatched part, which costs the performance SSR was meant to buy and can cause visible flicker. The cause is almost never React itself; it is a value that depends on where or when the code runs.

The usual causes, most common first

  1. Time-dependent rendering: new Date(), relative strings like “5 minutes ago”, copyright years, anything clock-based differs between the server's render moment and the client's.
  2. Random or generated values: Math.random(), generated ids, shuffled content, the two passes roll different dice.
  3. Locale and timezone formatting: toLocaleString() and date formatters use the server's locale and timezone on one pass and the visitor's on the other.
  4. Browser-only data used in the first render: localStorage values, window.innerWidth, a stored theme or username, the server renders the default, the client renders the stored value.
  5. Invalid HTML nesting changed by the browser: a <div> inside a <p>, table elements out of order, the browser “repairs” the server HTML before React compares, so the trees differ even though the markup was identical.

The fixes, in order

  1. Make the first client render produce exactly what the server produced: compute run-dependent values after mount in an effect and set them into state; the initial render matches, then the value updates.
  2. Pass the value down from the server when it must be in the first paint: serialize the date, seed, or locale into the page and render from that, so both passes use the same input.
  3. Fix invalid nesting when the mismatch is structural; validate the markup, browsers silently restructuring tables and paragraphs is a common hidden cause.
  4. Use suppressHydrationWarning only for the narrow, known cases it exists for, a timestamp that is intentionally server-stamped is the textbook example. It suppresses one element's text comparison, one level deep, and should carry a comment saying why.
Suppressing the warning globally, or keying whole trees to force re-renders, trades a visible bug for an invisible one. A mismatch means the user may have seen content flash or change; find which value differed. The development error message names the component and the differing text, start there.

Common questions

Is this the same as the Next.js hydration error? Yes, it is the React 18 wording of the same problem; Next.js surfaces it during page hydration. Older React versions worded it as a text content mismatch in a comment node or a failed checksum in even older releases.

Why does it only happen in development? Production React strips most of the comparison diagnostics, but the underlying mismatch still forces client re-rendering. Development is where it is cheap to find, fix it there.

Can an extension cause this? Browser extensions that modify page content before React hydrates (some translators and ad blockers) can trigger it on pages that are otherwise correct. Test in a clean profile before rewriting code.

Related errors

Next.js hydration error · Window is not defined · Objects are not valid as a React child