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个回答

3

我运行了这段代码,问题就解决了。

import "@/styles/globals.css";
import { useEffect, useState } from "react";

export default function App({ Component, pageProps }) {
  const [showChild, setShowChild] = useState(false);
  useEffect(() => {
    setShowChild(true);
  }, []);
  if (!showChild) {
    return null;
  }
  if (typeof window === "undefined") {
    return <></>;
  } else {
    return <Component {...pageProps} />;
  }
}


2

它将会工作:

 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;

在这里,我使用了recoil来进行状态管理。


2
在 Next v13 及以上版本中,您不应该在 <Link> 中使用 <a> 作为子元素。 如果您这样做,会出现错误。
在这种情况下,我会以另一种方式使用它:
const Child = () => <a>hello my friend</a>

const Parent = () => {
 return (
  <Link href="/">
    <Child />
  </Link>
 )  
}
  

这里出现了一个错误,为了解决它,我更改了子结构以删除<a>标签。


2

这个问题出现在 Next.js 中,因为 dangerouslySetInnerHTML 只支持 div 标签。如果您使用其他标签进行插入,则无法正常工作。

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

2
我已经在各种标签中使用了dangerouslySetInnerHTML,没有任何问题,你有关于你所说的声明的文档吗? - Stephane L
引用会很有帮助... - maxcountryman

2
如果您正在使用NextJS和Material UI与情感作为样式引擎,则可能需要检查组件的语义。您可以在记录到浏览器控制台的错误提示中找到提示。
例如:在Iconbutton内添加Box组件会导致错误。
无需创建自定义NextJS_document.js文件,因为@emotion/react版本10及以上默认与NextJS一起使用。

2
问题在于初始渲染的界面根据布局大小/父项大小的检测后发生变化。
我正在渲染一个需要使用窗口大小的组件。在初始渲染时,我的`useBreakpoint`钩子返回null,因为`window`对象是未定义的。
您可以利用useLayoutEffect来等待在初始渲染之前能够访问布局属性。
  const currentBreakpoint = useBreakpoint();

  const [isLayoutReady, setIsLayoutReady] = React.useState(false);

  React.useLayoutEffect(() => {
    setIsLayoutReady(true);
  }, []);

  const itemToShow =
    currentBreakpoint === Breakpoint.xl ? props.item : null;

  return (
   <div>
     {isLayoutReady && itemToShow}
    </div>
  )

1

当我试图将一个div放入React-Bootstrap的Card Text中时,我遇到了同样的问题。

可以通过以下步骤重现错误:

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

为了解决这个问题,我只是删除了HTML标签。
我认为一些React组件不接受内部的HTML标签。

1

由于初始 UI 与服务器呈现的不匹配,导致水合失败

您应该检查控制台是否有错误,例如:

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

并修复它们。

https://github.com/vercel/next.js/discussions/35773复制


1

我需要翻译的内容是关于IT技术的。以下是需要翻译的内容:

我的情况是在NextJS中在标签中使用没有的表格。

具体来说:

<table className="table">
  <thead>
    <th>head</th>
  </thead>
  <tbody>
    <tr>
      <td>content</td>
    </tr>
  </tbody>
</table>

然后我在 thead 标签内部添加了 tr 标签:

<table className="table">
  <thead>
    <tr>
      <th>head</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>content</td>
    </tr>
  </tbody>
</table>

现在问题已经解决。

1

我通过NextJs的动态导入并使用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 />
    )
}

这显然不是一个解决方案。 - JeffMinsungKim

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