Vuejs Error: The client-side rendered virtual DOM tree is not matching server-rendered

Vuejs Error: The client-side rendered virtual DOM tree is not matching server-rendered

我正在为我的应用程序使用 Nuxt.js / Vuejs,但我在不同的地方一直遇到这个错误:

    The client-side rendered virtual DOM tree is not matching server-rendered content. 
This is likely caused by incorrect HTML markup, for example nesting block-level elements inside <p>, or missing <tbody>. 
Bailing hydration and performing full client-side render.

我想了解调试此错误的最佳方法是什么?他们是我可以 record/get 客户端和服务器的虚拟 DOM 树以便我可以比较并找出错误所在的方法吗?

我的是一个大型应用程序,手动验证很困难。

部分答案:使用 Chrome DevTools,您可以定位问题并准确查看导致问题的因素。执行以下操作(我使用 Nuxt 5.6.0 和 Chrome 64.0.3282.186 执行此操作)

  1. 在 Chrome (F12)
  2. 中显示 DevTools
  3. 加载导致 "the client-side rendered virtual DOM tree..." 警告的页面。
  4. 滚动到 DevTools 控制台中的警告。
  5. 单击警告的源位置超链接(在我的例子中是 vue.runtime.esm.js:574)。
  6. 在那里设置一个断点(left-clicking 在源代码浏览器的行号处)。
  7. 再次出现同样的警告。我并不是说这总是可能的,但就我而言,我只是重新加载了页面。如果有很多警告,您可以通过将鼠标移到 msg 变量上来查看消息。
  8. 当您找到消息并在断点处停止时,请查看调用堆栈。向下单击一帧调用"patch"打开其源。将鼠标悬停在 patch 中执行行上方 4 行的 hydrate 函数调用上。指向 hydrate 源的超链接将打开。
  9. hydrate函数中,从开头移动大约15行,并在assertNodeMatch返回false之后返回false的地方设置断点。在那里设置断点并删除所有其他断点。
  10. 让同样的警告再次发生。现在,当遇到断点时,执行应该在 hydrate 函数中停止。切换到 DevTools 控制台并计算 elm,然后计算 vnode。这里的 elm 似乎是一个 server-rendered DOM 元素,而 vnode 是一个虚拟的 DOM 节点。 Elm 打印为 HTML 以便您找出错误发生的位置。

有关如何处理修改 DOM 的集成(例如 Google Analytics 或 FB Pixel)的示例,请参见此处。基本上创建一个插件并从 SSR 中排除。

https://nuxtjs.org/faq/ga

好吧,这听起来很傻。我尝试了大约 15 分钟的一堆不同的解决方案,例如重新启动服务器和删除 .nuxt 目录,但我懒得使用 @budden73 的大脑解决方案。最终对我有用的只是重新启动我的计算机,试一试。

到目前为止,我从观察中发现,当您使用像 jQuery(特别是)这样的第三方包时,它们有时会在 dom 中注入 html 标签。所以 Vue/Nuxt 失去了对 dom 树的跟踪并开始抱怨。

我遇到了同样的问题,过了一会儿我删除了所有 jQuery 并用 Vuejs 替换了 jQuery 功能,这些错误都消失了。

对我来说,这个错误发生是因为在 AsyncData 中获取数组列表并通过 v-for 呈现 <tr> 标签,我将 v-for 代码放在 <client-only> 块中问题解决了

这个错误调试起来真的很痛苦。为了快速找到导致问题的元素,请编辑 node_modules/vue/dist/vue.esm.js 并添加以下行:

// Search for this line: 
function hydrate (elm, vnode, insertedVnodeQueue, inVPre) {
    var i;
    var tag = vnode.tag;
    var data = vnode.data;
    var children = vnode.children;
    inVPre = inVPre || (data && data.pre);
    vnode.elm = elm;

    // Add the following lines: 
    console.log('elm', elm)
    console.log('vnode', vnode)
    console.log('inVpre', inVPre)
    // ...


您将在控制台中看到故障节点。

事实证明,就我而言,我有 HTML 个评论标签,这导致了这个愚蠢、烦人的错误。我花了太长时间才弄明白,但万一它对某人有帮助。

对于2.10以上的Nuxt版本,不需要安装任何东西,只需使用https://nuxtjs.org/api/components-client-only/中提到的默认组件<client-only>

查看之前的警告:

"nuxt": "^2.12.2"中,您可以从之前的警告中轻松找出原因。

以我为例:

不正确

<nuxt-link to="/game42day">
  <a>Game For Today</a>
</nuxt-link>

正确:

<nuxt-link to="/game42day">
  Game For Today
</nuxt-link>

我在实现 vue-particles 包时遇到了与 nuxt 版本 2.14.0 相同的问题。解决方法是用 no-ssr 包围标签,它解决了这个问题。

编辑:
解决方案的更新变体(如果 Nuxt 版本高于 2.9.0

<client-only>
  <vue-particles>
  </vue-particles>
</client-only>

旧解决方案:

<no-ssr>
  <vue-particles>
  </vue-particles>
</no-ssr>

在我的例子中,我不得不改变这个:

<v-expansion-panel-header v-text="name" />

对此:

<v-expansion-panel-header>{{ name }}</v-expansion-panel-header>

怎么样:

extend (config, ctx) {
  config.resolve.symlinks = false
}

看到这个

如果您使用 v-if 有条件地渲染组件,那么您有两种解决问题的方法:

第一个是将元素包装在 <no-ssr></no-ssr> 标签中。

第二种方法是用 v-show 替换 v-ifhere 是 Vue 文档的 link。

有很多方法可以解决这个问题,但大多数都不是真正的解决方法,只是一些老掉牙的创可贴。要注意几点:

  • 将其包装到 <client-only> 标签中,但要注意一些 important details
  • 使用 v-show 而不是 v-if
  • 试图破解一些生命周期
  • 等...

我强烈推荐阅读这篇由 Alexander Lichter 撰写的精彩文章

https://blog.lichter.io/posts/vue-hydration-error/

他会向您解释,您应该诊断发生这种情况的原因并解决实际问题。
基本上每次在服务器上生成的东西以及在客户端上完成补水时可用的东西都会导致这个错误。

其中一些是:

  • 无效 HTML(在 <p> 中有块元素,嵌套在另一个标签中的 a 标签也是如此...)
  • 第 3 方脚本扰乱您的组件
  • 服务器与客户端的不同状态
  • 任何随机都是有风险的(例如new Date()
  • 任何与身份验证相关的页面

我强烈建议您阅读这篇文章,以了解 Alexandre 自己的话如何处理此类问题。如果你赶时间,你总是可以使用一个创可贴修复,但尝试实际修复问题以获得最佳性能并保持代码干净。

检查是否在内联元素中使用了任何块级元素。

例如:里面,里面

如果您使用了 HTML table,请确保您使用了标签

由于这个问题,我也遇到了很多错误。我列举两个我经常遇到的案例,希望能帮到你。

  • 使用 vuetify 按钮,当你创建一个公共组件时,你应该使用:<v-btn>{{text}}</v-btn>。示例:
<template>
    <v-btn
      :width="width"
      :color="color"
      :class="[rounded ? 'rounded-pill' : 'rounded-lg',textColor]"
      v-on:click="onClick"
      elevation="0"
      :outlined="outlined"
      :type="type"
      :name="name"
      :form="form"
      :disabled="disabled"
      v-bind="$attrs"
    >{{ text }}</v-btn>
</template>
  • 不要将 v-html 与 <p> 标签一起使用。 不使用:<p v-html='html'></p>。 使用:<div v-html='html'></div>.

此外,如果你使用<client-only></client-only>,这个问题肯定是解决了,但是如果你需要SEO页面或者展示google广告,就不好解决了。

就我而言,我更改了我的代码

<p v-html="$md.render(post.content)"></p>

<p>{{ $md.render(post.content) }}</p>

感谢budden73的回答,我在调试过程中做了一点改进。

  1. 打开开发工具
  2. 单击 warn 消息,然后单击警告消息的第一行,您将被定向到 Sources 面板,文件名为 vue.runtime.esm.js?xxxx
  3. ctrl+f 在上面的文件中搜索 assertNodeMatch,不是函数,而是像:
    if (process.env.NODE_ENV !== 'production') {
      if (!assertNodeMatch(elm, vnode, inVPre)) {
        return false
      }
    }
  1. return false
  2. 行添加断点
  3. 刷新页面,断点触发
  4. 在“来源”面板左侧的 Scope->Local 下,单击 elm 元素,您将返回到 Elements 面板。
  5. 以上元素是client side渲染后的元素,与你的代码对比一下,看看有什么不同。

如果你找不到错误的来源,修复它的残酷方法是使用 nuxt 的 <client-only> 标签。

描述了另一种可能残酷的方式here。添加一个默认为false的isHydrate变量,在mounted钩子中设置为true,并在变量设置为true后渲染元素。