客户端呈现 HTML 和交互

使用 JavaScript 呈现 HTML 不同于呈现服务器发送的 HTML,这两者可能会影响性能。了解本指南的不同之处,以及您可以采取哪些措施来保持网站的呈现性能,尤其是在涉及互动的情况下。

默认情况下,对于使用浏览器内置导航逻辑(有时称为“传统网页加载”或“硬导航”)的网站,HTML 的解析和呈现表现非常出色。这些网站有时被称为多页应用 (MPA)。

不过,开发者可能会调整浏览器默认设置,以满足其应用程序的需求。对于使用单页应用 (SPA) 模式的网站来说,肯定是如此,该模式使用 JavaScript 在客户端上动态创建大部分 HTML/DOM。客户端渲染是此设计模式的名称,如果涉及的工作过多,可能会影响网站的 Interaction to Next Paint (INP)

本指南将帮助您权衡使用由服务器发送到浏览器发送的 HTML 与使用 JavaScript 在客户端上创建 HTML 之间的区别,以及后者如何在关键时刻导致较长的互动延迟。

浏览器如何呈现服务器提供的 HTML

传统网页加载中使用的导航模式涉及到在每次导航时从服务器接收 HTML。如果您在浏览器的地址栏中输入网址或点击 MPA 中的链接,则会发生以下一系列事件:

  1. 浏览器针对所提供的网址发送导航请求。
  2. 服务器以块的形式返回 HTML。

最后一步是关键。这也是服务器/浏览器交换中最基本的性能优化之一,称为流式传输。如果服务器可以尽快开始发送 HTML,并且浏览器没有等待整个响应到达,则浏览器可以在 HTML 到达时分块进行处理。

Chrome 开发者工具的性能面板中可视化的服务器所发送的 HTML 的解析屏幕截图。随着 HTML 不断流入,其数据块会在多个较短的任务中进行处理,呈现方式是增量的。
解析和渲染服务器提供的 HTML(如 Chrome 开发者工具的性能面板中所示)。解析和呈现 HTML 所涉及的任务会拆分为多个区块。

与浏览器中发生的大多数事情一样,解析 HTML 也发生在任务中。当 HTML 从服务器流式传输到浏览器时,浏览器会一次执行此操作,以块的形式到达,从而优化对该 HTML 的解析。结果是,浏览器在处理每个分块后会定期让出主线程,从而避免长时间运行的任务。这意味着,解析 HTML 时可能会发生其他工作,包括向用户显示页面所需的增量渲染工作,以及处理页面关键启动期间可能发生的用户互动。这种方法可以提高网页的 Interaction to Next Paint (INP) 得分。

从中可以看出,从服务器流式传输 HTML 时,您可以免费进行 HTML 的增量解析和呈现,并自动让出请求进入主线程。客户端渲染无法做到这一点。

浏览器如何呈现 JavaScript 提供的 HTML

虽然指向网页的每个导航请求都需要服务器提供一定量的 HTML,但有些网站会使用 SPA 模式。此方法通常包括:服务器提供最小的 HTML 初始负载,但之后客户端会使用从服务器获取的数据组合的 HTML,填充页面的主要内容区域。后续导航(在这种情况下有时称为“软导航”)完全由 JavaScript 处理,以便用新的 HTML 填充网页。

在更少数的情况下,非 SPA 中也可能会发生客户端呈现,即通过 JavaScript 将 HTML 动态添加到 DOM 中。

有几种常用方法可以创建 HTML 或通过 JavaScript 向 DOM 中添加内容:

  1. 借助 innerHTML 属性,您可以通过浏览器将其解析为 DOM 的字符串来设置现有元素上的内容。
  2. 借助 document.createElement 方法,您可以创建要添加到 DOM 的新元素,而无需使用任何浏览器 HTML 解析。
  3. 借助 document.write 方法,您可以将 HTML 写入文档(浏览器会对其进行解析,就像在方法 1 中一样)。不过,由于一些原因强烈建议不要使用 document.write
解析通过 Chrome 开发者工具的性能面板中可视化的 JavaScript 呈现的 HTML 的屏幕截图。工作发生在阻塞主线程的单个长任务中。
在客户端通过 JavaScript 解析和渲染 HTML,如 Chrome 开发者工具的性能面板中所示。解析和渲染过程中涉及的任务不会分块,从而导致耗时较长的任务阻塞主线程。

通过客户端 JavaScript 创建 HTML/DOM 的后果可能很明显:

  • 与服务器为响应导航请求而流式传输的 HTML 不同,客户端上的 JavaScript 任务不会自动分块,这可能会导致耗时较长的任务阻塞主线程。这意味着,如果您在客户端上一次性创建了过多的 HTML/DOM,则可能会对网页的 INP 产生负面影响。
  • 如果在客户端启动期间在客户端上创建了 HTML,浏览器预加载扫描程序便不会发现 HTML 中引用的资源。这无疑会对网页的 Largest Contentful Paint (LCP) 产生负面影响。虽然这不是运行时性能问题(而是在提取重要资源时出现网络延迟问题),但您不希望网站的 LCP 因为规避这一基本的浏览器性能优化而受到影响。

您可以采取哪些措施来应对客户端渲染对性能的影响

如果您的网站严重依赖于客户端呈现,并且您观察到字段数据中的 INP 值很差,那么您可能想知道客户端呈现是否与此问题有关。例如,如果您的网站是一个 SPA,则实测数据可能会揭示导致大量渲染工作的互动。

无论原因是什么,您都可以探索以下潜在原因,以便回到正轨。

尽可能多地从服务器提供 HTML

如前所述,默认情况下,浏览器以非常高性能的方式处理来自服务器的 HTML。它会以一种避免耗时长任务的方式分解 HTML 的解析和渲染,并优化主线程的总时间。从而缩短 Total Blocking Time (TBT),并且 TBT 与 INP 密切相关

您可能在依赖前端框架来构建网站。如果是,您需要确保在服务器上呈现组件 HTML。这将限制您的网站所需的初始客户端呈现量,并且应该会带来更好的体验。

  • 对于 React,您需要使用 Server DOM API 在服务器上呈现 HTML。但请注意:传统的服务器端渲染方法使用同步方法,这可能会导致较长的首字节时间 (TTFB) 以及首次内容绘制 (FCP) 和 LCP 等后续指标。请尽可能确保您使用的是适用于 Node.js其他 JavaScript 运行时的流处理 API,以便服务器可以尽快开始将 HTML 流式传输到浏览器。Next.js(一个基于 React 的框架)默认提供了许多最佳实践。除了在服务器上自动呈现 HTML 之外,它还可以为不会随用户环境(如身份验证)而变化的网页静态生成 HTML。
  • 默认情况下,Vue 还会执行客户端渲染。不过,与 React 一样,Vue 也可以在服务器上渲染组件 HTML。请尽可能利用这些服务器端 API,或考虑为 Vue 项目使用更高级别的抽象化,使最佳实践更易于实现。
  • 默认情况下,Svelte 在服务器上渲染 HTML,但是,如果您的组件代码需要访问浏览器专有的命名空间(例如 window),您可能无法在服务器上渲染该组件的 HTML。尽可能探索其他方法,以免导致不必要的客户端渲染。SvelteKit(在 Svelte 中称为“React”)尽可能地在您的 Svelte 项目中嵌入了许多最佳实践,以便您避免只使用 Svelte 的项目中可能存在的误区。

限制在客户端上创建的 DOM 节点的数量

当 DOM 较大时,渲染它们所需的处理时间往往会增加。无论您的网站是成熟的 SPA,还是由于与 MPA 的互动而在现有 DOM 中注入新节点,您都应考虑尽可能缩减这些 DOM 的大小。这将有助于减少客户端呈现期间显示相应 HTML 所需的工作量,并且有望降低您网站的 INP。

考虑流式 Service Worker 架构

这是一种先进技术,可能无法轻松用于所有用例,但它可将 MPA 变成一个网站,就像当用户从一个网页导航到另一个网页时,网站可以立即加载。您可以使用 Service Worker 在 CacheStorage 中预缓存网站的静态部分,同时利用 ReadableStream API 从服务器提取网页 HTML 的其余部分。

如果成功使用此方法,您就不会在客户端上创建 HTML,但是从缓存即时加载部分内容会给人以为网站正在快速加载的印象。采用这种方法的网站看起来就像 SPA,但不存在客户端呈现的下滑问题。还可以减少您向服务器请求的 HTML 数量

简而言之,流式 Service Worker 架构不会取代浏览器的内置导航逻辑,而是向其中添加。如需详细了解如何使用 Workbox 实现此目标,请参阅使用流更快地实现多页应用

总结

网站接收和呈现 HTML 的方式会影响性能。如果您依靠服务器发送网站正常运行所需的所有(或大部分)HTML,您将免费获得很多好处:增量解析和呈现,以及自动让出至主线程,以避免耗时较长的任务。

客户端 HTML 呈现会引入许多在许多情况下可以避免的潜在性能问题。然而,由于各个网站的要求,您不可能完全避免这种情况。为了减轻因客户端应用过多呈现可能产生的耗时长任务,请确保尽可能从服务器发送尽可能多的网站 HTML,针对必须在客户端呈现的 HTML,尽可能使用较小的 DOM 大小,并考虑使用其他架构,以加快 HTML 到客户端的传递,同时利用浏览器为从服务器加载的 HTML 提供的增量解析和呈现。

如果您能尽可能减少网站的客户端渲染,不仅会改善网站的 INP,还能改进其他指标,例如 LCP、TBT,在某些情况下甚至可能改进 TTFB。

主打图片来自 Unsplash 用户,由 Maik Jonietz 提供。