React 18: 水合失败,因为初始UI与服务器端渲染的不匹配。

244

我正在尝试在我的应用程序中让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发送给客户端

我需要动态生成如App类所示的<head></head>和<body></body>部分。


41
大家是否都同意,这个错误信息比没有任何帮助还要糟糕?! - Kraken
这个错误可能是由于像Dashlane或LastPass这样的扩展程序触发的,因为它们在渲染后注入HTML,从而破坏了服务器和客户端HTML结构之间的比较。请参考这里https://stackoverflow.com/a/75490907/5213598。 - undefined
61个回答

1

禁用特定组件的SSR

对于我来说,将导入的组件设置为动态组件并使用ssr:false的方式起作用。有关更多详细信息,请访问NextJs官方网站这里

import dynamic from 'next/dynamic'
 
const NoSSR = dynamic(() => import('../components/no-ssr'), { ssr: false })
 
export default function Page() {
  return (
    <div>
      <NoSSR />
    </div>
  )
}

0
我在使用NextJS和Material UI时遇到了同样的问题。 实际解决方案是使用NextJS的动态导入。 在下面的示例中,请不要导入头部组件。
import Header from "../components/header"

改为:

import dynamic from 'next/dynamic'

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

请查看下面的NextJS文档:

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


0
对我来说,原因是我忘记将<td>元素包装在<tr>元素内。这会导致错误。
 <thead className="w-full">
      <td className="w-1/12"></td>
      <td className="w-8/12"></td>
      <td></td>
    </thead>

正确的方式:

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

0

我曾经遇到过同样的问题。如果您正在使用像redux这样的状态管理器,最好使用一个布尔状态(比如'isHydrated'),并将初始值设为“false”。一旦初始化完成,将其设置为“true”。将此值用于每个具有动态 UI 渲染的组件,这些组件可能会遇到此错误。

请勿在组件级别放置此状态,否则这将影响初始渲染,因为该组件将重新创建带有初始值“false”的'isHydrated':

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>
  )
}

相反,做类似于这样的事情:

// 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>
      )
    }

0
我在Next.js和Faker.js中遇到了同样的问题,我只是使用条件渲染解决了它。我认为这是因为faker.js的值在页面第一次加载时发生了两次更改。下面的代码可能会对您有所帮助。

`

    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>
      )}
    </>
   );
}

`


0
您可以使用mui(material-ui)中的Nossr包装引起此错误的组件。
import NoSsr from "@mui/material/NoSsr";

<NoSsr> {your contents} </NoSsr>

获取更多信息:https://mui.com/material-ui/react-no-ssr/


1
这与此页面上的其他解决方案相同,它没有解决根本问题,这只会在客户端呈现。 - Joel Davey

0
我之前遇到了同样的问题,是因为在调用fetch api时没有使用useEffect钩子。我的API一直在重复获取JSON,因此next js给了我一个hydration错误。所以将api调用放在useEffect钩子中。

0
我强烈建议检查DOM并寻找不一致之处,并使用HTML代码检查工具。造成这种情况的原因很多,无法一一列举,但你检查DOM将能找到其他答案可能不正确的情况。

0

即使没有进行任何更改,我在使用create-next-app时也遇到了这个问题,并尝试了不同的浏览器和配置文件。我发现它们没有问题,所以我调查了我的扩展程序,但是它并没有解决问题。最后,我通过调查Chrome开发工具设置来解决了这个问题。我的问题与“启用本地覆盖”有关。因此,我取消了勾选,问题得到了解决。

在Chrome设置中启用本地覆盖

1


0

如果你正在使用PM2将NextJs应用部署到AWS上,那么你只需要重新构建并重启应用程序进程即可。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接