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
我在渲染React组件树时遇到了一个错误,因为我错误地导入了一个包。

1

当我移动NextJs中的页面目录时,遇到了这个问题。通过删除.next文件夹并使用yarn build重新构建它,我解决了这个问题。


1

在我的情况下,这是一个嵌套列表中的用户错误。我忘记在

  • 中添加
      ,所以它只是嵌套的

  • 1

    确保用Suspense包装你导入的懒加载模块。

    在我的情况下,我导入了

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

    但是我一直像使用普通模块一样使用它

    <Footer />
    

    我用Suspense包装它,错误就不见了。

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

    底线

    如果在首页出现此错误,请尝试注释您使用的一些组件,直到找到错误的来源。


    1
    我的应用程序是一个NEXT JS应用。
    我使用的是 react-use-cart 模块,它似乎存在与 react @18.0.0 版本不兼容的问题。
    我不确定如何可能出现这种情况,但将版本降级到 react @17.0.2 后,错误就消失了。
    之前我使用的是 react @18.0.0。
    我只需运行 npm uninstall react react-dom 并安装版本 @17.0.2 即可。
    万事大吉,现在一切都按预期工作。

    嘿!不需要降级,只需重新格式化从react-use-cart使用的对象即可。更明确地说,items对象应该是Json字符串化,然后在另一个变量中进行json解析,而该变量则代替items: /// const { items } = useCart() const [allItems, setallItems] = useState([{}]) useEffect(() => { setallItems(JSON.parse(JSON.stringify(items))) }, [items]) /// 对于cartTotal数字也是一样,只需将其设置为useState常量即可。 - Firas SCMP

    1

    这肯定是一个与标签相关的问题。如果您正在使用NextJS作为前端框架,我建议您在使用子组件的组件中使用“Box”标签。这将允许您在子组件内成功使用大多数标签。为了更加安全,您可以使用模板字面量来包装子组件。


    1
    这是由于使用基于服务器-客户端的条件渲染所导致的。
    例如:如果您根据窗口的存在使用一个块(即在React完成渲染并且我们在客户端上时),但它在服务器端渲染的条件下不同,那么当初始用户界面与服务器端渲染(SSR)不匹配时,渲染失败。
    typeof window !== 'undefined' ? <div>something</div> : <div> something else</div>
    

    通过上述代码,你正在将服务器端渲染与客户端渲染不匹配,避免使用这些条件,并采用不同的方法来修复此错误,即使用useState钩子将此逻辑保持在函数外部,在componentDidMount阶段使用useEffect钩子并将空数组作为第二个属性进行初始设置。

    1

    请确保您没有嵌套 next/Link,我需要重构代码并忘记了在包装图像之前有一个 next/Link

    例如:

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

    在我的remix应用程序中,我有一个嵌套在<Link/>内的<Link/>。移除内部的<Link/>后,浏览器警告消失了。 - Sam Henderson

    1
    这个问题发生在我尝试将我的组件渲染为客户端渲染时,与hydration不匹配。所以我们可以在客户端渲染期间禁用hydration来解决这个问题。
    下面给出的方法对我起作用了。
    import dynamic from 'next/dynamic' 
    
    function Mycomponent(){
       return (
         <h1>Hello world<h1/>
      )
    }
    
    export default dynamic(() => Promise.resolve(Mycomponent), { ssr: false });
    
    

    1
    根据上述解决方案,可以确定错误本身并不太具有说明性,是的,问题出在标签放置错误上。
    在我的情况下,我正在使用NextJS 13.4版本,并且我想要显示一个单独创建的Navbar组件,所以我想将它添加到我的根布局中。我对Navbar的应用方式是错误的。
    错误的示例:
    export default function RootLayout({ children }) {
      return (
        <html lang="en" className={d_script.className}>
          <Navbar />
          <body> {children} </body>
        </html>
      );
    }
    

    正确

    export default function RootLayout({ children }) {
      return (
        <html lang="en" className={d_script.className}>
          <body>
            <Navbar />
            {children}
          </body>
        </html>
      );
    }
    

    如同Navabar显示在主体中一样,它应被包含在其中,并且这也是一个标签问题,所以会出现错误消息“由于初始UI与服务器上渲染的不匹配而导致的水合失败”。

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