docs: Improve hydration mismatch error guide. (#52481)

This commit is contained in:
Lee Robinson 2023-07-09 20:41:42 -07:00 committed by GitHub
parent 922498e886
commit 4f8f9ae5b0
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23

View file

@ -4,102 +4,82 @@ title: 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](https://react.dev/reference/react-dom/hydrate).
While rendering your application, there was a difference between the React tree that was pre-rendered from the server and the React tree that was rendered during the first render in the browser (hydration).
This can cause the React tree to be out of sync with the DOM and result in unexpected content/attributes being present.
[Hydration](https://react.dev/reference/react-dom/client/hydrateRoot) is when React converts the pre-rendered HTML from the server into a fully interactive application by attaching event handlers.
### Common Causes
Hydration errors can occur from:
1. Incorrect nesting of HTML tags
1. `<p>` nested in another `<p>` tag
2. `<div` nested in a `<p>` tag
2. Using checks like `typeof window !== 'undefined'` in your rendering logic
3. Using browser-only APIs like `window` or `localStorage` in your rendering logic
4. [Browser extensions](https://github.com/facebook/react/issues/24430) modifying the HTML
5. Incorrectly configured [CSS-in-JS libraries](https://nextjs.org/docs/app/building-your-application/styling/css-in-js)
1. Ensure your code is following [our official examples](https://github.com/vercel/next.js/tree/canary/examples)
## 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.
The following strategies can help address this error:
An example:
### Solution 1: Using `useEffect` to run on the client only
```jsx filename="example.js"
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>
Ensure that the component renders the same content server-side as it does during the initial client-side render to prevent a hydration mismatch. You can intentionally render different content on the client with the `useEffect` hook.
```jsx
import { useState, useEffect } from 'react'
export default function App() {
const [isClient, setIsClient] = useState(false)
useEffect(() => {
setIsClient(true)
}, [])
return <h1>{isClient ? 'This is never prerendered' : 'Prerendered'}</h1>
}
```
How to fix it:
During React hydration, `useEffect` is called. This means browser APIs like `window` are available to use without hydration mismatches.
```jsx filename="example.js"
// 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>
}
```
### Solution 2: Disabling SSR on specific components
Another example:
Next.js allows you to [disable prerendering](https://nextjs.org/docs/app/building-your-application/optimizing/lazy-loading#skipping-ssr) on specific components, which can prevent hydration mismatches.
Invalid HTML may cause hydration mismatch such as div inside p.
```jsx
import dynamic from 'next/dynamic'
```jsx filename="example.js"
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>
)
}
```
const NoSSR = dynamic(() => import('../components/no-ssr'), { ssr: false })
How to fix it:
```jsx filename="example.js"
export const CorrectComponent = () => {
export default function Page() {
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" />
<NoSSR />
</div>
)
}
```
Common causes with css-in-js libraries:
### Solution 3: Using `suppressHydrationWarning`
- When using Styled Components / Emotion
- When css-in-js libraries are not set up for pre-rendering (SSR/SSG) it will often lead to a hydration mismatch. In general this means the application has to follow the Next.js example for the library. For example if `pages/_document` is missing and the Babel plugin is not added.
- Possible fix for Styled Components:
- If you want to leverage Styled Components with SWC in Next.js 12.1+ you need to [add it to your Next.js config under compiler options](/docs/architecture/nextjs-compiler#styled-components): https://github.com/vercel/next.js/tree/canary/examples/with-styled-components
- If you want to use Styled Components with Babel, you need `pages/_document` and the Babel plugin: https://github.com/vercel/next.js/tree/canary/examples/with-styled-components-babel
- Possible fix for Emotion: https://github.com/vercel/next.js/tree/canary/examples/with-emotion
- When using other css-in-js libraries
- Similar to Styled Components / Emotion css-in-js libraries generally need configuration specified in their examples in the [examples directory](https://github.com/vercel/next.js/tree/canary/examples)
Sometimes content will inevitably differ between the server and client, such as a timestamp. You can silence the hydration mismatch warning by adding `suppressHydrationWarning={true}` to the element.
Local Overrides:
```jsx
<time datetime="2016-10-25" suppressHydrationWarning />
```
It's possible you may have [Local Overrides enabled in Chrome devtools](https://developer.chrome.com/blog/new-in-devtools-65/#overrides). 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 iOS issues
Common causes on iOS:
iOS attempts to detect phone numbers, email addresses, and other data in text content and convert them into links, leading to hydration mismatches.
- 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](https://github.com/vercel/next.js/issues/38290). This can be disabled with the following `meta` tag:
This can be disabled with the following `meta` tag:
```html filename="HTML output"
```jsx
<meta
name="format-detection"
content="telephone=no, date=no, email=no, address=no"
/>
```
## Useful Links
- [React Hydration Documentation](https://react.dev/reference/react-dom/client/hydrateRoot)
- [Josh Comeau's article on React Hydration](https://www.joshwcomeau.com/react/the-perils-of-rehydration/)