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

aus*_*ser 258 reactjs next.js

我试图让SSR在我的应用程序中工作,但出现错误:

Hydration 失败,因为初始 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;
Run Code Online (Sandbox Code Playgroud)

文件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>
);
Run Code Online (Sandbox Code Playgroud)

现场演示中显示的 HTML 模板由后端提供服务并使用以下代码生成:

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

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

// Serve clientHtml to client
Run Code Online (Sandbox Code Playgroud)

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

小智 368

我最近在 Next.js 中遇到了同样的问题,我不确定我的观察结果是否适用于其他库。

我一直用不正确的标签包装我的组件,也就是说,Next.js 不喜欢用p标签包装你的divsections等,所以它会大喊“水合失败,因为初始 UI 与渲染的内容不匹配”在服务器上”。

我通过检查元素如何相互包裹来解决这个问题。对于Material UI,您需要谨慎。例如,如果您使用排版组件作为包装器,则组件属性的默认值为“p”,因此如果您不将组件值更改为某种语义,您将遇到错误。

在我看来,根据我个人的经验,该问题是由 HTML 元素排列不当引起的,要在 Next.js 的上下文中解决该问题,必须重新评估他们如何排列 HTML 元素。

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>
  )
}
Run Code Online (Sandbox Code Playgroud)

  • React 的 Github 上的这条评论很好地总结了这个问题 https://github.com/facebook/react/issues/24519#issuecomment-1122780621。 (6认同)

小智 73

导入和运行某些包也可能导致此错误。例如,当我使用 Swiper.js 包时,我遇到了这个问题。这主要是因为包在某处使用 Window 对象。

由于修改包本身的内容并不是一个好的做法,因此解决此类问题的最佳方法是仅在加载 DOM 后渲染组件。所以你可以试试这个。

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

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

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

export default Index;
Run Code Online (Sandbox Code Playgroud)

  • 但是如果滑块占据屏幕尺寸的一半并位于页面顶部怎么办?这会产生比水合问题更严重的 CLS 问题 (2认同)

dgk*_*nca 35

如果您使用的是table,您可能错过了<tbody>

错误:

<table>
  <tr>
    <td>a</td>
    <td>b</td>
  </tr>
</table>
Run Code Online (Sandbox Code Playgroud)

正确的:

<table>
  <tbody>
    <tr>
      <td>a</td>
      <td>b</td>
    </tr>
  </tbody>
</table>
Run Code Online (Sandbox Code Playgroud)

  • 拯救了我的一天...谢谢! (2认同)

San*_*Ali 21

那么让我解释一下为什么您的 Next.js 应用程序中会出现此错误。

有些标签不能在另一个标签内使用。例如,您不能在标签div内使用td。您不能pspan. table其他标签如、ul等也是如此。

更新:https: //html.spec.whatwg.org/上的文章可以帮助理解为什么会出现此错误。

您需要转到代码并删除所有使用的无效标签。就我而言,我使用了一个无效的a标签。span

这是无效的

<span>
  <a target="_blank" href="https://askhumans.io"> Login </a>
</span>
Run Code Online (Sandbox Code Playgroud)

这是有效的。

  <a target="_blank" href="https://askhumans.io"> <span>  Login </span> </a>
Run Code Online (Sandbox Code Playgroud)


Min*_*ang 17

这使得应用程序在客户端呈现。这个对我有用:

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>
    );
  }
}
Run Code Online (Sandbox Code Playgroud)

我还使用Next.jsRedux Toolkit。

  • 我不认为这是一个解决方案,您阻止服务器渲染组件。您还可以跳过第一个前端渲染。你的解决办法就是不喝水。 (47认同)
  • 这使得应用程序客户端渲染,而不是解决方案 (6认同)

han*_*ast 11

该错误具有误导性,因为它与水合作用无关,而是与错误嵌套的标签有关。我通过删除 HTML 内容并重新加载页面来找出冲突的标签,直到找到问题(二分搜索)。

就我而言,这是由<a>内部引起的<Link>。我认为Next.js 需要<a>链接内的 ,但显然情况并非如此/不再是这样。

请参阅有关tag的 Next.js 文档<Link>


Lig*_*ght 7

删除<p>标签解决了我与Next.js 的类似问题。


小智 5

我有 React 18.2.0 和 Next.js 12.2.4,我用以下代码修复了水合作用:

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
Run Code Online (Sandbox Code Playgroud)

  • 由于 useEffect 仅在客户端中运行,因此此代码有效地阻止了服务器端渲染的发生。那可能不是你想要的。 (14认同)

小智 5

只需进入浏览器,Chrome \xe2\x86\x92 右上角三栏按钮(汉堡菜单) \xe2\x86\x92 更多工具 \xe2\x86\x92 清除浏览历史记录 \xe2\x86\x92 删除 cookies。

\n

不再有错误。

\n

  • 这确实有效。有时,这些错误似乎实际上并不有效。对我来说,我清除了正在开发的本地主机应用程序上的本地存储,它解决了问题。 (2认同)