rsnext/errors/react-hydration-error.md
Josh Parnham 5451564f36
[docs] Add iOS hydration mismatch details to error page (#43584)
We recently ran into hydration mismatch errors on iOS, which we realised
was due to iOS automatically converting what it thought were phone
numbers into links.

We then found this GitHub issue,
https://github.com/vercel/next.js/issues/38290, and thought it would be
useful to add these details to the documentation page linked from the
next.js error.

---

Example from the linked issue:


![image](https://user-images.githubusercontent.com/712727/204925813-f986e2ca-29c2-4b63-9121-df4c7b00b1d8.png)

## Documentation / Examples

- [x] Make sure the linting passes by running `pnpm build && pnpm lint`
- [x] The "examples guidelines" are followed from [our contributing
doc](https://github.com/vercel/next.js/blob/canary/contributing/examples/adding-examples.md)

Co-authored-by: Tim Neutkens <tim@timneutkens.nl>
2023-05-09 12:55:41 +02:00

5 KiB

Text content does not match server-rendered HTML

Why This Error Occurred

While rendering your application, there was a difference between the React tree that was pre-rendered (SSR/SSG) and the React tree that rendered during the first render in the Browser. The first render is called Hydration which is a feature of React.

This can cause the React tree to be out of sync with the DOM and result in unexpected content/attributes being present.

Possible Ways to Fix It

In general this issue is caused by using a specific library or application code that is relying on something that could differ between pre-rendering and the browser. An example of this is using window in a component's rendering.

An example:

function MyComponent() {
  // This condition depends on `window`. During the first render of the browser the `color` variable will be different
  const color = typeof window !== 'undefined' ? 'red' : 'blue'
  // As color is passed as a prop there is a mismatch between what was rendered server-side vs what was rendered in the first render
  return <h1 className={`title ${color}`}>Hello World!</h1>
}

How to fix it:

// In order to prevent the first render from being different you can use `useEffect` which is only executed in the browser and is executed during hydration
import { useEffect, useState } from 'react'
function MyComponent() {
  // The default value is 'blue', it will be used during pre-rendering and the first render in the browser (hydration)
  const [color, setColor] = useState('blue')
  // During hydration `useEffect` is called. `window` is available in `useEffect`. In this case because we know we're in the browser checking for window is not needed. If you need to read something from window that is fine.
  // By calling `setColor` in `useEffect` a render is triggered after hydrating, this causes the "browser specific" value to be available. In this case 'red'.
  useEffect(() => setColor('red'), [])
  // As color is a state passed as a prop there is no mismatch between what was rendered server-side vs what was rendered in the first render. After useEffect runs the color is set to 'red'
  return <h1 className={`title ${color}`}>Hello World!</h1>
}

Another example:

Invalid HTML may cause hydration mismatch such as div inside p.

export const IncorrectComponent = () => {
  return (
    <p>
      <div>
        This is not correct and should never be done because the p tag has been
        abused
      </div>
      <Image src="/vercel.svg" alt="" width="30" height="30" />
    </p>
  )
}

How to fix it:

export const CorrectComponent = () => {
  return (
    <div>
      <div>
        This is correct and should work because a div is really good for this
        task.
      </div>
      <Image src="/vercel.svg" alt="" width="30" height="30" />
    </div>
  )
}

Common causes with css-in-js libraries:

Local Overrides:

It's possible you may have Local Overrides enabled in Chrome devtools. With this enabled, the HTML served will be different from what the SSR emitted. It also won't show up in view-source, so you may be left wondering what is going on.

Common causes on iOS:

  • iOS attempts to detect phone numbers, email addressees and other data in text content and convert them into links, which can lead to hydration mismatches. This can be disabled with the following meta tag:
<meta name="format-detection" content="telephone=no, date=no, email=no, address=no" />