Next.js - 预期的服务器 HTML 在 <div> 中包含匹配的 <div>

Posted

技术标签:

【中文标题】Next.js - 预期的服务器 HTML 在 <div> 中包含匹配的 <div>【英文标题】:Next.js - Expected server HTML to contain a matching <div> in <div> 【发布时间】:2021-08-19 12:20:29 【问题描述】:

提供实时示例here

我正在尝试制作一个基本布局,在该布局中,在手机上仅显示最新帖子。在桌面上,左栏应该是帖子,右栏应该是热门类别和最受欢迎的帖子。

这是布局:

const IndexLayout: React.FC<IndexLayoutProps> = () => 
  const cols = useScreenType()

  return cols === '2-cols' ? (
    <div className="w-full flex justify-between items-start">
      <ListPosts data-comp="ListPosts" className="w-4/6" />
      <div className="sticky ml-12 w-2/6 flex flex-col">
        <TopCategories data-comp="TopCategories" className="w-full" />
        <PopularPosts data-comp="PopularPosts" className="mt-4" />
      </div>
    </div>
  ) : (
    <ListPosts data-comp="ListPosts" className="w-full" />
  )

这是useScreenType 钩子:

import  useMediaQuery  from 'react-responsive'

export const useScreenType = () => 
  const is2Cols = useMediaQuery( minWidth: 1300 )
  const is1Cols = useMediaQuery( minWidth: 800 )

  if (is2Cols) 
    return '2-cols'
  

  if (is1Cols) 
    return '1-cols'
  

  return 'fullscreen'

我不断收到此错误:

Warning: Expected server html to contain a matching <div> in <div>.
div
ListPosts@webpack-internal:///./components/posts/ListPosts.tsx:31:19
div
IndexLayout@webpack-internal:///./components/layout/IndexLayout.tsx:28:149
div
Index@webpack-internal:///./pages/index.tsx:24:149
ApolloProvider@webpack-internal:///./node_modules/@apollo/client/react/context/ApolloProvider.js:13:18
s@webpack-internal:///./node_modules/next-apollo/dist/index.es.js:26:1911
div
div
MyApp@webpack-internal:///./pages/_app.tsx:37:19
ErrorBoundary@webpack-internal:///./node_modules/@next/react-dev-overlay/lib/internal/ErrorBoundary.js:23:47
ReactDevOverlay@webpack-internal:///./node_modules/@next/react-dev-overlay/lib/internal/ReactDevOverlay.js:73:20
Container@webpack-internal:///./node_modules/next/dist/client/index.js:155:20
AppContainer@webpack-internal:///./node_modules/next/dist/client/index.js:643:18
Root@webpack-internal:///./node_modules/next/dist/client/index.js:779:19

现在我认为问题是由于useScreenType 钩子无法获得宽度,因为window 未在服务器上定义。但是我该如何解决这个问题?不仅我得到一个错误,而且我的 HTML 呈现奇怪。

最终的渲染结果是这样的(当它渲染为“2-cols”时):

<div class="flex flex-col justify-start items-start w-full">
  <div class="mt-6 w-full"></div>
  <div class="mt-4 flex items-center cursor-pointer transform transition hover:scale-105 text-sm">
    <div class="w-full p-6 rounded-lg flex flex-col dark:bg-gray-800 shadow-md"></div>
    <div class="mt-4 p-6 rounded-lg flex flex-col dark:bg-gray-800 shadow-md"></div>
  </div>
</div>

注意:我使用的是 Next.js v10.2.0

代码可以在GitHub找到

【问题讨论】:

如您所见,您无法访问服务器上的窗口对象,因此如果您想基于窗口对象进行服务器渲染 - 您必须对这些值进行硬编码 我不确定这是否真的与缺少对 window 对象的访问有关,您可能会得到一个实际错误,而不是像现在这样的警告。您可以尝试查看 s-s-r example 的 react-responsive,看看这是否是无效标记的原因。 很可能是由于缺少窗口对象的错误被处理并使用了某种回退,这给我们留下了警告。 【参考方案1】:

如您所见,您无法访问服务器上的窗口对象,因此如果您想基于窗口对象在服务器上渲染某些内容 - 您必须对这些值进行硬编码。

您唯一可以依赖的是请求标头中的用户代理,它可以让您对用户设备有所了解。

例如,您可以通过这种方式在 _app.js 中检测用户设备:

const device = deviceDetector.detect(isServer() ? ctx.req.headers['user-agent'] : window.navigator.userAgent)

deviceDetector 是任何一种基于用户代理的设备检测实现

【讨论】:

是的,如果我的页面是 s-s-r 就可以了。索引页面甚至不是服务器渲染的,这让我感到困惑...... 据我所知,每个页面都是 s-s-r,除非你指定它不是。 我知道这个页面不是服务器端渲染的。那么这个bug的原因是什么呢?【参考方案2】:

对于任何想知道我如何解决此问题的人,我放弃了带有逻辑的响应式设计并改用 CSS。这是我的布局后修复(用lg 前缀[documentation] 更改了一些类):

const IndexLayout: React.FC<IndexLayoutProps> = () => 
  return (
    <div className="mt-12 lg:mt-24 w-5/6 mx-auto flex items-start">
      <div className="w-full flex justify-between items-start">
        <ListPosts className="lg:w-4/6 w-full" />
        <div className="hidden sticky ml-12 w-2/6 lg:flex flex-col">
          <TopCategories className="w-full" />
          <PopularPosts className="mt-4" />
        </div>
      </div>
    </div>
  )

【讨论】:

以上是关于Next.js - 预期的服务器 HTML 在 <div> 中包含匹配的 <div>的主要内容,如果未能解决你的问题,请参考以下文章

Next.js:错误:React.Children.only 预期接收单个 React 元素子项

在 React 中渲染格式化(未缩小的)HTML(带有 Next.js 的 s-s-r)

Next Js 错误:元素类型无效:预期为字符串(对于内置组件)或类/函数(对于复合组件)但得到:未定义

Next js 和 google 分析在 nginx 反向代理后面不起作用

如何在服务器端渲染中将样式应用于 Next.js 中的 HTML?

react-html5video 是不是与服务器端渲染兼容? (使用 next.js)