# Hydration failed because the initial UI does not match what was rendered on the server

> Hydration failed because the initial UI does not match what was rendered on the server: React 18's error, the warning that names the element, and fixes.

Source: https://hydration.jscrate.dev/docs/errors/hydration-failed-initial-ui-does-not-match
Last updated: 2026-09-18

"Hydration failed because the initial UI does not match what was rendered on
the server" is React 18's error for a structural mismatch: the first render in
the browser has a different element than the server's HTML. React discards
the HTML and renders the page again. The warning logged just before it names
the element to fix.

## The error

In development, React 18 logs a warning that names the element, then throws
the error, then reports that it switched to client rendering:

```text
Warning: Expected server HTML to contain a matching <div> in <div>.
Uncaught Error: Hydration failed because the initial UI does not match what was rendered on the server.
Uncaught Error: There was an error while hydrating. Because the error happened outside of a Suspense boundary, the entire root will switch to client rendering.
```

Next.js 13 and 14 add a link to the error:

```text
Error: Hydration failed because the initial UI does not match what was rendered on the server.
See more info here: https://nextjs.org/docs/messages/react-hydration-error
```

A production build prints the code instead:

```text
Minified React error #418; visit https://reactjs.org/docs/error-decoder.html?invariant=418 for the full message or use the non-minified dev environment for full errors and additional helpful warnings.
```

React 19 renamed it to
["Hydration failed because the server rendered HTML didn't match the client"](https://hydration.jscrate.dev/docs/errors/hydration-failed-server-rendered-html-didnt-match-client)
and added a diff.

## What hydration failed because the initial UI does not match what was rendered on the server means

React 18 hydrates by walking the server HTML and your first client render side
by side. The React 18 "Hydration failed because the initial UI does not match"
error is thrown when it expects an element and finds a different one, or finds
nothing. Text is checked separately: a text difference throws
["Text content does not match server-rendered HTML"](https://hydration.jscrate.dev/docs/errors/text-content-does-not-match-server-rendered-html)
instead.

After the error, React renders the nearest `<Suspense>` boundary, or the whole
root, again in the browser. That is the third line:
["There was an error while hydrating"](https://hydration.jscrate.dev/docs/errors/there-was-an-error-while-hydrating).
The page works, but it renders twice, can flash, and loses any input the user
typed before hydration.

The error itself never says which element. The warning above it does:

| Warning                                                               | Meaning                                                            |
| --------------------------------------------------------------------- | ------------------------------------------------------------------ |
| `Expected server HTML to contain a matching <div> in <div>`           | The browser rendered a `<div>` the server HTML does not have there |
| `Did not expect server HTML to contain a <div> in <div>`              | The server HTML has a `<div>` the browser did not render           |
| `validateDOMNesting(...): <div> cannot appear as a descendant of <p>` | Invalid nesting that the browser rewrote before React ran          |

React 18 logs only the first mismatch per page load. If you fix one, reload:
the next one appears.

Next.js 13 and 14 print the React 18 wording, so in Next.js 13 "Hydration
failed because the initial UI does not match" is what any structural mismatch
looks like, in both routers. It is a common error: the Stack Overflow question
with this message as its title has about 690,000 views.

## Common causes

Anything that renders a different element in the browser than on the server:

- **Invalid HTML nesting.** A `<div>` inside a `<p>`, or a `<tr>` without
  `<tbody>`. The browser repairs the server HTML while parsing it, so React
  finds a different tree. See [invalid HTML nesting](https://hydration.jscrate.dev/docs/causes/invalid-html).
- **A `typeof window` branch.** `typeof window !== 'undefined' ? <A /> : <B />`
  renders `<B />` on the server and `<A />` in the browser. See
  [browser-only APIs](https://hydration.jscrate.dev/docs/causes/browser-api).
- **Screen size, theme or storage read during render.** A mobile menu chosen
  from `window.innerWidth`, a logged-in header chosen from `localStorage`. See
  [media queries](https://hydration.jscrate.dev/docs/causes/media-query), [theme](https://hydration.jscrate.dev/docs/causes/theme) and
  [storage](https://hydration.jscrate.dev/docs/causes/storage).
- **Different data.** The browser fetched the list again and got one more
  item. See [server and client data](https://hydration.jscrate.dev/docs/causes/data).
- **Something edited the HTML first.** A browser extension, a third-party
  script or a CDN that minifies HTML. See
  [browser extensions](https://hydration.jscrate.dev/docs/causes/extension),
  [third-party scripts](https://hydration.jscrate.dev/docs/causes/third-party-script) and
  [CDNs](https://hydration.jscrate.dev/docs/causes/cdn).

## How to fix it

1. **Run the app in development** and find the warning logged before the
   error. It names the element and its parent, and the component stack under
   it names your component.
2. **Decide why that element differs.** Use the causes above.
3. **Render the same element on both sides.** A layout that depends on the
   screen size is a common case. Before, the server always renders the desktop
   navigation:

   ```tsx title="components/nav.tsx"
   "use client";

   import { DesktopNav, MobileNav } from "./navs";

   export function Nav() {
     const isMobile = typeof window !== "undefined" && window.innerWidth < 768;
     return isMobile ? <MobileNav /> : <DesktopNav />;
   }
   ```

   After, both navigations are in the HTML and CSS picks one, so the server
   and the browser render the same elements:

   ```tsx title="components/nav.tsx"
   import { DesktopNav, MobileNav } from "./navs";

   export function Nav() {
     return (
       <>
         <div className="md:hidden">
           <MobileNav />
         </div>
         <div className="hidden md:block">
           <DesktopNav />
         </div>
       </>
     );
   }
   ```

   When the value must come from the browser, render a default first and read
   it in an effect: see
   [useEffect and two-pass rendering](https://hydration.jscrate.dev/docs/guides/useeffect-two-pass-rendering).

4. **Reload and check the console** for the next mismatch.
5. **Put unstable parts in a `<Suspense>` boundary.** React then re-renders
   only that boundary when hydration fails, not the whole root.

## Find every instance

hydration-proof loads every route, compares the server HTML with the hydrated
DOM and names the element, even in production builds where React only prints
#418:

```bash
npx hydration-proof test
```

Structural differences are [HP1007](https://hydration.jscrate.dev/docs/issues/hp1007) (a different
element), [HP1008](https://hydration.jscrate.dev/docs/issues/hp1008) (only in the server HTML) and
[HP1009](https://hydration.jscrate.dev/docs/issues/hp1009) (missing from it). Nesting the browser repaired
is [HP3001](https://hydration.jscrate.dev/docs/issues/hp3001), and a page React rendered again is
[HP1011](https://hydration.jscrate.dev/docs/issues/hp1011). Add `--mode development` to get React's own
warnings attached.

The [ESLint plugin](https://hydration.jscrate.dev/docs/eslint) flags the usual sources while you type:
[`no-window-render-branch`](https://hydration.jscrate.dev/docs/rules/no-window-render-branch),
[`no-match-media-in-render`](https://hydration.jscrate.dev/docs/rules/no-match-media-in-render),
[`no-storage-in-initial-render`](https://hydration.jscrate.dev/docs/rules/no-storage-in-initial-render) and
[`no-invalid-interactive-nesting`](https://hydration.jscrate.dev/docs/rules/no-invalid-interactive-nesting).

## Related

- [Expected server HTML to contain a matching element](https://hydration.jscrate.dev/docs/errors/expected-server-html-to-contain-a-matching)
- [Minified React error #418 in production](https://hydration.jscrate.dev/docs/errors/minified-react-error-418)
- [Fix Next.js hydration errors](https://hydration.jscrate.dev/docs/frameworks/nextjs)
- [How to debug hydration errors](https://hydration.jscrate.dev/docs/guides/debug-hydration-errors)
- [Every React hydration error message](https://hydration.jscrate.dev/docs/errors)
