React 16: 警告:由于状态问题,期望服务器HTML包含与<div>匹配的<div>。

82

使用SSR时出现以下错误:

警告: 期望服务器HTML在<div>中包含匹配的<div>。

问题出现在组件挂载时,在客户端检查浏览器宽度,然后设置组件的状态以渲染其移动版本。

但是服务器默认为桌面容器的版本,因为它不知道浏览器的宽度。

如何处理这种情况?我是否可以在服务器上某种方式检测浏览器宽度,并在发送到客户端之前呈现移动容器?

编辑:目前,我已决定在组件挂载时呈现容器。 这样,服务器和客户端都不会最初呈现任何内容,从而防止此错误。

我仍然愿意接受更好的解决方案。


如果您正在使用SSR,则请确保使用React.hydrate()方法,否则请使用React.render()。您还可以在呈现的元素上使用suppressHydrationWarning={true}属性。但不要过度使用它。 - AxeEffect
8个回答

36

这将解决问题。

// Fix: Expected server HTML to contain a matching <a> in
const renderMethod = module.hot ? ReactDOM.render : ReactDOM.hydrate;
renderMethod(
  <BrowserRouter>
    <RoutersController data={data} routes={routes} />
  </BrowserRouter>,
  document.getElementById('root')
);

1
最后,你需要重新启动服务器。然而,如果你在客户端和服务器端使用相同的组件,那么这并没有什么区别。但是,如果你想要在服务器更新时也自动重新加载浏览器,你仍然需要自动重新加载服务器。 - html_programmer
2
这怎么解决问题的?如果启用了热模块重载,似乎只需使用SPA方法进行客户端渲染。但在生产构建中,仍然会存在标记不匹配的问题吗? - gaurav5430
20
在Next.js中怎么处理呢?因为在public文件夹中没有根id。 - Mirza Irtiza
1
这是做什么的? - samuelnihoul

22

Gatsby

Gatsby的一个最新功能标志(于2020年12月的v2.28版本中引入)可以在开发环境下实现服务器端渲染页面。

该标志默认设置为true。此时,您可能会在控制台看到以下错误信息:

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

您可以在 gatsby.config.js 文件中禁用此标志:

module.exports = {
  flags: {
    DEV_SSR: false,
  }
}

文档: https://www.gatsbyjs.com/docs/reference/release-notes/v2.28/#feature-flags-in-gatsby-configjs

功能标志在Gatsby配置文件中的特性。

DEV_SSR - 在开发期间完全重新加载时在服务器端呈现(SSR)页面。帮助您检测SSR错误并修复它们,无需进行完整构建。这将解决警告,但会禁用其他功能。 - Mossen
4
这只是解决开发模式警告的问题,如果您在生产环境中进行服务器端渲染,则无法避免标记差异。 - gaurav5430

10

这条消息也可能是由于坏代码导致的,它不能在服务器端呈现一致的内容和客户端呈现一致的内容, 因此hydrate无法解析。

例如 SSR 返回:

...
<div id="root">
   <div id="myDiv">My div content</div>
</div>
...

当CSR返回时:

...
<div id="root">
    <div id="anotherDiv">My other div content</div>
</div>
...

在这种情况下,最好的解决方案不是安装库或关闭hydrate,而是实际修复代码中的不一致性。
暂时从index.js中删除<script src="/react-bundle-path.js"></script>可以帮助比较SSR渲染的确切内容和CSR hydrate渲染的内容。

9

目前被接受的答案与TypeScript不兼容。以下是适用于我的方法。

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <link rel="shortcut icon" href="/favicon.ico" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
  </head>
  <body>
    <noscript>
      You need to enable JavaScript to run this app.
    </noscript>
    <div id="root"></div>
  </body>
</html>

import React from "react"
import { hydrate, render } from "react-dom"
import BrowserRouter from "./routers/Browser"

const root = document.getElementById("root")
var renderMethod
if (root && root.innerHTML !== "") {
  renderMethod = hydrate
} else {
  renderMethod = render
}
renderMethod(<BrowserRouter />, document.getElementById("root"))

这个解决方案对我有效,React/React-DOM ^16.13.0。 - Simeon

3

来自 https://github.com/vercel/next.js/discussions/17443#discussioncomment-87097

只应在浏览器中运行的代码应该在 useEffect 中执行。这是必需的,因为第一次渲染应该与服务器的初始渲染匹配。如果您操纵该结果,则会创建不匹配,React 将无法成功地进行页面水合。

当您在 useEffect 中运行仅在浏览器中运行的代码(例如尝试访问 window)时,它将发生在水合之后


1

2
您提供的链接已经失效,请将相关信息粘贴在这里以备将来参考。 - CorwinCZ
@CorwinCZ 谢谢您的通知。我已经更新了链接并添加了另一个链接。 - Martin Ždila

1
我的解决方案是使用类似express-useragent的中间件来检测浏览器用户代理。
然后,在服务器端,按照以下规则创建一个viewsize,如{width, height}
if (ua.isMobile) {
  return {width: 360, height: 480}
}

if (ua.isDesktop) {
  return {width: 768, height: 600}
}

return {width: 360, height: 480} // default, and for bot

然后,在SSR中它仍然以某种方式是响应式设计。


0
在我的情况下,我必须将"Redux提供者"和"Toaster"放置在根布局的body标签中。
export default function RootLayout({ children }) {
      return (
        <>
          <html lang="en">
            <body className={inter.className}>
            <Toaster position="top-right" reverseOrder={false} toastOptions={{ duration: 1000 }} />
              <StoreProvider>
                {children}
              </StoreProvider>
            </body>
          </html>
        </>
      )
    }

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