Vue-SSR 客服端激活失败(Vue hydration fails)

这是我参与更文挑战的第 16 天,活动详情查看: 更文挑战前端

Lynne,一个能哭爱笑永远少女心的前端开发工程师。身处互联网浪潮之中,热爱生活与技术。vue

前言

前一阵遇到了一个Bug.....node

Error: [nuxt] Error while mounting app: HierarchyRequestError: Failed to execute 'appendChild' on 'Node':
This node type does not support this method. at some-file.js:1
复制代码

整整排查了2个多小时,若是要追究其根本缘由,找到了这篇文章,解释完美。web

原文地址:Vue激活失败(blog.lichter.io/posts/vue-h…浏览器

服务端渲染有不少好处,特别是当像Nuxt.js或GridSome这样的网站,不管是使用动态SSR仍是生成静态网站,开发 Vue-SSR 应用程序都是一件垂手可得的事。但从另外一方面来说,服务端渲染也会带来从未见过的复杂性和错误。尽管大多数错误都被记录在案且提供了变通的解决方案,但一个错误仍让不少人困惑:Vue激活失败。服务器

1、什么是 Vue激活失败

激活是当Vue转换服务端渲染标记并使之反应的过程,所以使它能反映 Vue 的变化。 若是Vue期待与渲染的HTML不一样的标记,就会发生激活失败。markdown

在Vue-SSR中是这样解释的:Vue 在浏览器端接管由服务端发送的静态HTML标记,并使其变为由Vue管理的动态DOM的过程。app

理解:服务端已经渲染好了HTML,无需将其丢弃再从新建立全部的 DOM元素,而是去激活这些渲染好的静态HTML,使他们成为动态的以可以响应后续的数据变化。async

SSR + 客户端混合 -- 浏览器会更改一些特殊的HTML结构,致使与Vue生成的虚拟DOM结构不匹配。ide

2、如何辨认激活失败

咱们如今意识到激活是什么和在何时会失败,可是咱们做为开发者如何发现激活没有如预期通常工做呢?有两条错误消息确定会指出激活失败但都有限制条件。

1. 第一条是仅在开发中出现,不管哪一种模式:

Parent:  <div class="container"> client-hook-3.js:1:16358
Mismatching childNodes vs. VNodes: NodeList(3) [ p, p, p ]  Array [ {…} ]
    
[Vue warn]: 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.
复制代码
  1. 第二条错误消息是仅在生产环境和静态生成站点时:
Error: [nuxt] Error while mounting app: HierarchyRequestError: Failed to execute 'appendChild' on 'Node':
This node type does not support this method. at some-file.js:1
复制代码

众所周知,激活仅在页面首先由服务端渲染时发生,所以一般仅在你应用的初始化请求中。

(asyncData 及 data中---客户端的data会和asyncData中的data混合)

由于当经过一个标签导航时激活失败是不可见的而仅在硬重载时才可见,这使得发现激活失败问题变得更加困难。

所以激活错误有时仅在分级系统或者更糟,仅在生产环境下被发现。在极少数状况下,甚至不会打印出错误而仅仅时某些组件中止工做。

3、通常引起错误的缘由

如今咱们了解了如何发现激活错误,咱们将研究致使Vue激活错误的典型缘由。固然咱们不可能覆盖全部可能的缘由,由于它们差异很大,并且主要取决于你的代码。

在如下章节中,每次提到服务端渲染,它就与两种状况都相关(动态SSR和静态站点生成),由于从技术上讲,二者都具备服务端渲染内容。 (除非另有声明)

1. 不合理的HTML

当激活失败发生时不合理的HTML是你应该检查的第一个地方,这也应该是有错误信息提示的:

This is likely caused by incorrect HTML markup, for example nesting block-level elements inside <p>, or missing <tbody>
复制代码

不幸的是,不合理的HTML一般不是激活失败的缘由。不过,你应该仔细检查你的标记。另外,你还要确保检查缩小设置,由于过分的HTML缩小可能会致使无效的HTML。若是您有用户生成的输出或来自CMS的内容,则值得验证此内容也是有效的HTML。最后,第三方插件或服务也可能影响和操纵HTML。后者的一个常见示例是Cloudflare,当您启用了它们的服务。如HTML缩小,Rocket loader或其余更改页面内容的功能时。

我建立了一个简单的示例codeandbox,其中包含无效的HTML并触发了激活失败。

2. 修改HTML的脚本

关于脚本:若是你向你的Vue应用中插入第三方JS文件,也能够在Vue接收并激活来自服务器的HTML以前更改HTML

3. 服务器和客户端的状态不一样

服务器和客户端上状态不一致是发生激活失败最多见的缘由。像往常同样,不一致的缘由千差万别。

1)日期、时间戳和随机化

当您的网站包含日期或者时间戳时,应尽量当心并使其尽量静态,尤为在您的网站是静态生成的状况下。若是客户端评估像new Date() 这样的表达式,则该表达式可能会与在你服务器上开发阶段检索相同日期时生成的日期不一样。这也让我对公司的“关于”页面感到困惑,在该页面上,我想根据当前分钟对显示的人员进行排序。

export const deterministicRotate = (arr) => {
  if (arr.length <= 1) {
    return arr
  }
  const rotations = (new Date()).getMinutes() % arr.length
  return rotations ? arr : arr.reverse()
}
复制代码

若是用户打开页面的时间很奇怪,则计划将阵列反转。当使用动态SSR时效果很好。但当切换到静态生成的JAMstack站点时,该功能就会成为一个Bug。你能够在一分钟后点击连接刷新,会发现名字和人正确的交换了,但图片和原来一致。糟糕!这是因为服务器和客户端时间不匹配致使的。在移除不肯定型洗牌代码后工做恢复正常。

2)受权

不一致的另外一个常见缘由是用户身份验证。这适用于动态SSR和静态站点生成。

当仅在客户端(例如,在localStorage中)上存储身份验证状态时,服务器“不知道身份验证”。这将不可避免地致使激活问题,由于登陆时服务器和客户端信息根本不一样。所以,若是服务器不知道正在静态生成您的页面的身份验证状态,则不该在服务器端呈现任何与身份验证相关的组件。

您可能想知道为何它老是适用于静态网站:由于当您生成网站时,它是HTML,而序列化的代码是“无状态的”。在构建阶段,咱们没法考虑“已登陆的用户状态”。这意味着您必须从服务器上的渲染中排除全部与身份验证相关的组件。

3)其余缘由

除了这两种状况外,还有更多边缘状况可能会打击您并引发不一致。即便未在此处列出,咱们也将解决激活错误!首先,咱们将其范围缩小到致使问题的DOM元素。

4、解决激活失败问题

1. 发现致使激活失败的元素

咱们可使用您最喜好的浏览器上的devTools缩小问题到一个特定的组件或者DOM元素。

1)确保你在开发环境下

2)打开开发调试工具

3)触发激活警告(一般经过重载网页)

4)展开 [Vue Warn] The client side ... 错误消息查看追踪堆栈(取决于浏览器,也打开弹出的VueJS列表)

5)点击一个激活回调,将会打开Vue激活函数的源代码

6)如今,不管什么时候这个函数返回false都设置一个debugger,在撰写文本时,这种状况发生了三遍:

if (process.env.NODE_ENV !== 'production') {
  if (!assertNodeMatch(elm, vnode, inVPre)) {
      return false //HERE
  }
}
if (process.env.NODE_ENV !== 'production' &&
          typeof console !== 'undefined' &&
          !hydrationBailed
    ) {
        hydrationBailed = true;
        console.warn('Parent: ', elm);
        console.warn('server innerHTML: ', i);
        console.warn('client innerHTML: ', elm.innerHTML);
    }
  return false //HERE
}
 if (process.env.NODE_ENV !== 'production' &&
      typeof console !== 'undefined' &&
      !hydrationBailed
  ) {
    hydrationBailed = true;
    console.warn('Parent: ', elm);
    console.warn('Mismatching childNodes vs. VNodes: ', elm.childNodes, children);
  }
  return false //HERE
}
复制代码

这一样容许在激活失败前检查激活函数的参数。

7)最后但一样重要的是,让激活错误复现,一般再次重载页面是有可能的但有时更困难。

8)你如今看到触发了咱们的一个断点,脚本中止执行了

9)如今打开调试工具的控制台,在激活失败的地方写一个元素去获取DOM元素。使用DOM元素,你将可以将激活错误追溯到你的Vue组件之一

10)继续执行下一步

PS:这是用户budden73对此StackOverflow答案的改编工做流。

2. 确保你的HTML标记合理

如今你发现了致使问题的代码,你首先要作的事确保你的标记(也许来自一个API)是合理的。像这样的代码

Text

无效,由于一个p元素不容许在其中包含其余块元素(如段落标签)。

可是注意,标记不容许像

这样的标签做为子元素。这些标记是Vue过渡的默认标记。你能够经过进行改变。

3. 解决服务器与客户端之间的不一致

在debugger期间,你可以从服务器看到结果和从新绘制客户端侧。若是存在不一样,你能够看一看,你如何获取数据和你在服务端或客户端渲染了什么。一个常见的问题是静态网页的认证。由于HTML在构建时生成的是无状态的,所以不知道任何受权状态,你应用的全部和受权有关的部分都应该旨在客户端从新渲染。不然,在客户端有受权状态的用户,由于登陆而指望从服务端获取不一样的HTML。而后只剩下一个选项...

4. 最终避免措施:

最后一个解决激活错误的选择是彻底避免组件出现激活错误。这对于在静态生成的页面上与身份验证相关的组件来讲是必须的,有时对于交付您不能更改但必须嵌入的内容的组件(例如,来自第三方应用程序)也是必需的。

正如咱们在一开始了解到的,激活仅发生在组件被同时渲染在服务端和客户端时。为了不激活失败,咱们经过标签避免从新渲染服务端组件。 惟一的缺点:该组件不包含在服务器返回的HTML中,对SEO没有帮助。

5、总结

让咱们结束吧,如今你了解更多:

什么是激活以及它作了什么?

激活怎么失败的以及如何发现激活失败?

激活失败的通常缘由

如何调试激活失败及解决你的应用程序

我但愿这篇文章颇有见识,而且您学到了一两件事。您是否遇到了此处未描述的激活错误缘由,或者我错过了一个常见缘由?随时在Twitter上或经过邮件给我发消息。 并且像往常同样-若是您能宣传并与同事分享博客文章,我将很高兴。

总结

看完这篇文章收获很大,但愿你也是~~~