反应18: 水合作用失败,因为初始 UI 与服务器上呈现的内容不匹配

我试图让 SSR 工作在我的应用程序,但我得到了错误:

水合作用失败,因为初始 UI 不匹配 呈现在服务器上。

现场演示代码是 给你

问题的现场演示是 给你(打开开发工具控制台查看错误) :

//App.js

 import React from "react";
    

class App extends React.Component {


head() {
return (
<head>
<meta charSet="utf-8" />
<meta
name="viewport"
content="width=device-width, initial-scale=1, shrink-to-fit=no"
/>
<meta name="theme-color" content="#000000" />
<title>React App</title>
</head>
);
}


body() {
return (
<body>
<div className="App">
<h1>Client says Hello World</h1>
</div>
</body>
);
}


render() {
return (
<React.Fragment>
{this.head()}
{this.body()}
</React.Fragment>
)
}
}
export default App;

//index.js

import React from "react";
import * as ReactDOM from "react-dom/client";
import { StrictMode } from "react";


import App from "./App";




// const container = document.getElementById("root");
const container = document.getElementsByTagName("html")[0]


ReactDOM.hydrateRoot(
container,
<StrictMode>
<App />
</StrictMode>
);

实时演示中显示的 Html 模板由后端提供,并使用以下代码生成:

const ReactDOMServer = require('react-dom/server');


const clientHtml = ReactDOMServer.renderToString(
<StrictMode>
<App />
</StrictMode>
)

//向客户端提供 clientHtml

我需要动态生成 <head></head> and <body></body>部分,如 App 类中所示

125054 次浏览

I had similar issues. Reverting back to a stable version of react worked the magic

Consider reverting back to React 17

Here is a github discussion (https://github.com/vercel/next.js/discussions/35773)

This solution work for me:

export default function MyApp({ Component, pageProps }: AppProps) {
const [showChild, setShowChild] = useState(false);
useEffect(() => {
setShowChild(true);
}, []);


if (!showChild) {
return null;
}


if (typeof window === 'undefined') {
return <></>;
} else {
return (
<Provider store={store}>
<Component {...pageProps} />
</Provider>
);
}
}

I also using NextJS, Redux Toolkit

it will work:

 function MyApp({ Component, pageProps }) {
const [showing, setShowing] = useState(false);


useEffect(() => {
setShowing(true);
}, []);
    

if (!showing) {
return null;
}
    

if (typeof window === 'undefined') {
return <></>;
} else {
return (
<RecoilRoot>
<MainLayout>
<Component {...pageProps} />
</MainLayout>
</RecoilRoot>
);
}
}


export default MyApp;

here I used recoil for state managing.

So mine is a NEXT JS app.

I am using the react-use-cart module and it seems it has issues with react @18.0.0.

I am not sure how this is possible but downgrading to react @17.0.2 removed my errors.

Previously I was using react @18.0.0

I simply ran npm uninstall react react-dom and installed versions @17.0.2.

Wahala, everything now works as expected.

I have been experiencing the same problem lately with NextJS and i am not sure if my observations are applicable to other libraries. I had been wrapping my components with an improper tag that is, NextJS is not comfortable having a p tag wrapping your divs, sections etc so it will yell "Hydration failed because the initial UI does not match what was rendered on the server". So I solved this problem by examining how my elements were wrapping each other. With material UI you would need to be cautious for example if you use a Typography component as a wrapper, the default value of the component prop is "p" so you will experience the error if you don't change the component value to something semantic. So in my own opinion based on my personal experience the problem is caused by improper arrangement of html elements and to solve the problem in the context of NextJS one will have to reevaluate how they are arranging their html element.

import Image from 'next/image'
/**
* This might give that error
*/
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>
)
}


/**
* This will work
*/
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>
)
}

Removing the <p> tag solved my similar problem with NEXTJS..

If you're using NextJS and Material UI with emotion as the styling engine, then you may need to check the semantics of your components. You can find hints in the errors logged to the browser console.

Example: adding Box component inside Iconbutton will cause an error

There's no need to create a custom NextJS _document.js file because @emotion/react version 10 and above works with NextJS by default.

I had the same issue when tried to put a div inside Card Text of React-Bootstrap.

The error can be reproduced by:

import type { NextPage } from 'next'
import { Card } from 'react-bootstrap';
...
const testPage : NextPage = () => {
...
return (
...
<Card.Text>
<div>It's an error</div>
</Card.Text>
...
)}


export default testPage

To fix it, i just removed the html tag.

I think that some react components doesn't accept html tags inside.

Importing and running some of the packages can cause this error too. For example, when I used Swiper.js package I encountered this problem. It's mostly because the package is using Window object somewhere.

Since it isn't a good practice to modify the content of the package itself, the best way to tackle such issues is to render the component only after the DOM is loaded. So you can try this.

const Index = () => {
const [domLoaded, setDomLoaded] = useState(false);


useEffect(() => {
setDomLoaded(true);
}, []);


return (
<>
{domLoaded && (
<Swiper>
<div>Test</div>
</Swiper>
)}
</>
);
};


export default Index;

I had the same issue with Next.js and Faker.js, and i just use conditional rendering and it's solved. I think it happened because the values from faker.js changes twice when page first loading. Code below may help you.

`

    export default function Story() {
const [avatar, setAvatar] = useState(null);
const [name, setName] = useState(null);


useEffect(() => {
setAvatar(faker.image.avatar());
setName(faker.name.findName());
}, []);


return (
<>
{avatar && name && (
<div>
<Image
src={avatar}
alt="Story Profile Picture"
width={50}
height={50}
/>
<p>{name}</p>
</div>
)}
</>
);
}

`

You Can wrap your component that cause this error with Nossr from mui(material-ui)

import NoSsr from "@mui/material/NoSsr";


<NoSsr> {your contents} </NoSsr>

to get more info: https://mui.com/material-ui/react-no-ssr/

Make sure to wrap in Suspense the lazy modules you import.

In my case I imported

const Footer = React.lazy(() => import('../Footer/Index'));

but I was using it just like a normal module

<Footer />

I wrapped it in Suspense and the error was gone.

<Suspense fallback={<div>Loading...</div>}>
<Footer />
</Suspense>

Bottom line

If this error is given to you on the home page, try to comment some of the components you use until you find where the error is coming from.

In my case, when I used reverse() function before mapping my list, I was getting a similar error.

In my case, neither making the HTML more semantic nor modifying the _app.tsx to check if the window was loaded.

The only solution, for now, was to downgrade the React version and the React DOM.

I'm using NextJS.

I switched to version 17.0.2.

If you're using a table, you probably missed <tbody>

incorrect:

<table>
<tr>
<td>a</td>
<td>b</td>
</tr>
</table>

correct:

<table>
<tbody>
<tr>
<td>a</td>
<td>b</td>
</tr>
</tbody>
</table>

In my case, it's an user error in nested list. I forgot adding a ul in li so it was just nested lis.

Issue got fixed to me by changing tag to tag

Make sure you dont have next/Link nested, I needed to refactor the code and forgod that I had a next/Link before wrapping the image.

for example

        <CompanyCardStyle className={className}>
//Open Link
<Link href={route('companyDetail', { slug: company.slug })}>
<a className='d-flex align-items-center'>
<div className='company-card'>
<div className='d-flex align-items-center col-name-logo'>
<div className='company-logo'>
//Remove link and let the <a> child
<Link href={route('companyDetail', { slug: company.slug })}>
<a><img src={company.logoUrl} width={'100%'} /></a>
</Link>
</div>
<h6 className='mb-0'>{company.name}</h6>
</div>
.....
</div>
</a>
</Link>
</CompanyCardStyle>

It happened to me in Nextjs and I found out I accidentally moved the index.js file into another folder. So I returned it to the pages folder and all was well.

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

You should check the console for errors like:

Warning: Expected server HTML to contain a matching <div> in <div>.

and fix them.

Copied from https://github.com/vercel/next.js/discussions/35773

I just faced the similar error. In my case I was also using Nextjs, basically the problem was that I was not using the tag inside the tag. And usually, browsers automatically adds the if I just place inside the tag, and don't specify the either tbody or thead tag inside the table tag.

I believe this can apply to all other similar HTML tags and it was telling in the error, that something is rendering on the DOM which you've not mentioned in code.

One more thing, I was using the nested loop in that particular section first to map the table rows and then a certain table data field. When I removed the nested map it worked without adding the tbody and also worked when I added the tboday but kept the nested loop. So, I'm not sure why it's weird. However, it's working so f* it.

This is working for me to get client side rendering throughout the app. No more worries about using window throughout the rest of the app.

import useSSR from 'use-ssr'


function MyApp(props) {
const { Component, pageProps } = props


return (
<>
<Head>
<title>title</title>
<meta name='description' content='Generated by create next app' />
<link rel='icon' href='/favicon.ico' />
</Head>
<ClientSideRenderedNextJS>
<Component {...pageProps} />
</ClientSideRenderedNextJS>
</>
)
}


function ClientSideRenderedNextJS({ children }) {
const [domLoaded, setDomLoaded] = useState(false)
  

useEffect(() => {
setDomLoaded(true)
}, [])


const { isServer } = useSSR()
if (!domLoaded) return null
if (isServer) return <></>


return children
}

In my case, I faced this problem because I had used <img /> tag instead of Next.js <Image /> tag because I couldn't use tailwind classes with the <Image /> tag. Replacing the <img /> tag with Next.js <Image /> tag solved the issue for me. Then I wrapped the <Image /> tag with a div tag and added the classes to that.

I have react 18.2.0 with next 12.2.4 and I fix hydration with this code

import { useEffect, useState } from 'react'
import { Breakpoint, BreakpointProvider } from 'react-socks';
import '../styles/globals.scss'


function MyApp({ Component, pageProps }) {
const [showChild, setShowChild] = useState(false)


useEffect(() => {
setShowChild(true)
}, [])


if (!showChild) {
return null
}


return (
<BreakpointProvider>
<Component {...pageProps} />
</BreakpointProvider>
)
}


export default MyApp

I ran into same issue using NextJS with Material UI. Actual solution is to use NextJS dynamic import. In the example below do not import header component as:

import Header from "../components/header"

Instead do:

import dynamic from 'next/dynamic'


const DynamicHeader = dynamic(() => import('../components/header'), {
suspense: true,
})

Check the NextJS documentation below:

https://nextjs.org/docs/advanced-features/dynamic-import

I had this issue when I moved the pages directory in NextJs. I solved it by deleting the .next folder and rebuilding it using yarn build.

This works for me, Nextjs.

export default function MyApp({ Component, pageProps }: AppProps) {
const [showChild, setShowChild] = useState(false);
useEffect(() => {
setShowChild(true);
}, []);


if (!showChild) {
return null;
}


if (typeof window === 'undefined') {
return <></>;
} else {
return (
<Provider client={state}>
<Component {...pageProps} />
</Provider>
);
}

}

I had this issue even with create-next-app without any changes and tried with different browser and different profiles. I see that there is no problem with them so I investigate my extensions but it didn't solve the problem. Finally I've solved it with investigating chrome dev tools settings. My problem related with "Enable Local Overrides". So I've unchecked that and it solved.

Enable Local Overrides on Chrome Settings

1

I solved this problem by NextJs dynamic import with ssr: false

import dynamic from 'next/dynamic'
import { Suspense } from 'react'


const DynamicHeader = dynamic(() => import('../components/header'), {
ssr: false,
})


export default function Home() {
return (
<DynamicHeader />
)
}

just go to browser, chrome->three bars button on top right corner->more tools->clear browsing history-> delete cookies.

no more error

If you're deploying the NextJs app to AWS using PM2. You just have to rebuild and restart your app process.

this issue comes in nextjs because dangerouslySetInnerHTML support only div tag. if you insert with other tag its not work.

 <div dangerouslySetInnerHTML=\{\{__html:data.description}}></div>

For me the reason was I forgot to wrap <td> element inside <tr> element.

gives you error.

 <thead className="w-full">
<td className="w-1/12"></td>
<td className="w-8/12"></td>
<td></td>
</thead>

the correct way:

 <thead className="w-full">
<tr>
<td className="w-1/12"></td>
<td className="w-8/12"></td>
<td></td>
</tr>
</thead>

For me the issue was caused by date-fns package. After removing the component that was using the date formatting, the error message which was showing up only after deploying went away. Hope this helps.

If u use html tags u want to place them in correct way and correct order. in NEXTJS.

ex: If u use table.

-> U must add the tbody tag

<!-- Wrong -->


<table>
<tr>
<td>Element</td>
<td>Element</td>
</tr>
</table>


<!-- Correct Way -->


<table>
<tbody> <!-- This is a Must -->
<tr>
<td>Element</td>
<td>Element</td>
</tr>
</tbody>
</table>

The error is misleading as it's not about hydration but wrongly nested tags. I figured out the conflicting tags by removing html and reloading the page until I found the problem (binary search).

In my case it was caused by <a> within <Link>. I thought next.js requires the <a> within the Link but obviously that's not/no longer the case.

See next.js documentation about <Link> tag

Just go main parent component and set the state:

const [isSSR,setIsSSR]=React.useState(false);
React.useEffect(()=>{
setIsSSR(true);
})
return (
isSSR && (
<>
//your component here
</>
)
)

I was getting this same error in NextJs when trying to work using context and localstorage, the problem was solved by doing a check in _app.

I had the same issue. If you're using a state management like redux, better to have a boolean state like 'isHydrated' with initial value of 'false'. Once the initial ui is initialized, set it to 'true'. Consume this value in every component with dynamic ui rendering that will possibly encounter with this error.

DO NOT put this state at the component level, this will affect initial rendering as the component will recreate 'isHydrated' with initial value of 'false':

function App(){
const [isHydrated, setIsHydrated] = useState(false)


useEffect(() => {
setIsHydrated(true)
}, []);


return (
<div>
{isHydrated ? <p>Hello</p> : null}
</div>
)
}






function Component(){
const [isHydrated, setIsHydrated] = useState(false)


useEffect(() => {
setIsHydrated(true)
}, []);


return (
<div>
{isHydrated ? <p>Hello</p> : null}
</div>
)
}

INSTEAD, do something like this:

// initialize ui rendering


function App(){
const isHydrated = useSelector(s => s.isHydrated)
    

useEffect(() => {
dispatch({ type: 'SET_IS_HYDRATED' })
}, []);
    

return (
<div>
{isHydrated ? <p>Hello</p> : null}
</div>
)
}




// just consume the state at the component level and will render correctly when
// navigating to other page with 'isHydrated' value of 'true'


function Component(){
const isHydrated = useSelector(s => s.isHydrated)
    

return (
<div>
{isHydrated ? <p>Hello</p> : null}
</div>
)
}