使用 JavaScript 渲染 HTML 与渲染服务器发送的 HTML 不同,这可能会影响性能。在本指南中了解两者之间的区别,以及您可以采取哪些措施来保持网站的渲染性能,尤其是在涉及交互方面。
默认情况下,对于使用浏览器内置导航逻辑(有时称为“传统页面加载”或“硬导航”)的网站,浏览器非常擅长解析和渲染 HTML。此类网站有时称为多页应用程序 (MPA)。
但是,开发人员可能会绕过浏览器默认设置以满足其应用程序需求。对于使用单页应用程序 (SPA) 模式的网站来说,情况确实如此,该模式使用 JavaScript 在客户端动态创建 HTML/DOM 的大部分内容。客户端渲染是这种设计模式的名称,如果涉及的工作过多,它可能会对您网站的交互到下次绘制 (INP)产生影响。
本指南将帮助您权衡使用服务器发送到浏览器的 HTML 与在客户端使用 JavaScript 创建 HTML 之间的区别,以及后者如何在关键时刻导致高交互延迟。
浏览器如何渲染服务器提供的 HTML
传统页面加载中使用的导航模式涉及在每次导航时接收来自服务器的 HTML。如果您在浏览器的地址栏中输入 URL 或单击 MPA 中的链接,则会发生以下一系列事件
- 浏览器发送针对所提供 URL 的导航请求。
- 服务器以分块形式响应 HTML。
这些步骤的最后一步是关键。它也是服务器/浏览器交换中最基本的性能优化之一,称为流式传输。如果服务器可以尽快开始发送 HTML,并且浏览器不等待整个响应到达,则浏览器可以在 HTML 到达时以分块形式处理 HTML。

与浏览器中发生的大多数事情一样,HTML 解析发生在任务中。当 HTML 从服务器流式传输到浏览器时,浏览器会优化 HTML 的解析,方法是在流的各个部分以块的形式到达时,一次处理一点。结果是,浏览器在处理完每个块后会定期让位于主线程,从而避免长任务。这意味着在解析 HTML 的同时可以进行其他工作,包括呈现页面给用户所需的增量渲染工作,以及处理在页面关键启动期间可能发生的用户交互。这种方法可以提高页面的交互到下次绘制 (INP)得分。
要点是什么?当您从服务器流式传输 HTML 时,您可以免费获得 HTML 的增量解析和渲染,以及自动让位于主线程。客户端渲染则无法实现这一点。
浏览器如何渲染 JavaScript 提供的 HTML
虽然每个页面的导航请求都需要服务器提供一些 HTML,但有些网站会使用 SPA 模式。这种方法通常涉及服务器提供的最小初始 HTML 有效负载,但随后客户端将使用从服务器获取的数据组装的 HTML 填充页面的主要内容区域。后续导航(在这种情况下有时称为“软导航”)完全由 JavaScript 处理,以使用新的 HTML 填充页面。
客户端渲染也可能发生在非 SPA 中,在更有限的情况下,HTML 通过 JavaScript 动态添加到 DOM。
有几种常见的方法可以通过 JavaScript 创建 HTML 或添加到 DOM
innerHTML
属性允许您通过字符串设置现有元素上的内容,浏览器会将该字符串解析为 DOM。document.createElement
方法允许您创建要添加到 DOM 的新元素,而无需使用任何浏览器 HTML 解析。document.write
方法允许您将 HTML 写入文档(并且浏览器会解析它,就像方法 #1 中一样)。但是,由于多种原因,强烈建议不要使用document.write
。

通过客户端 JavaScript 创建 HTML/DOM 的后果可能很严重
- 与服务器响应导航请求而流式传输的 HTML 不同,客户端上的 JavaScript 任务不会自动分块,这可能会导致阻塞主线程的长任务。这意味着,如果您一次在客户端上创建过多的 HTML/DOM,则页面的 INP 可能会受到负面影响。
- 如果在启动期间在客户端上创建 HTML,则浏览器预加载扫描程序不会发现其中引用的资源。这肯定会对页面的最大内容绘制 (LCP)产生负面影响。虽然这不是运行时性能问题(而是获取重要资源的网络延迟问题),但您不希望网站的 LCP 因规避这种基本的浏览器性能优化而受到影响。
您可以采取哪些措施来应对客户端渲染的性能影响
如果您的网站严重依赖客户端渲染,并且您已观察到现场数据中 INP 值较差,您可能想知道客户端渲染是否与该问题有关。例如,如果您的网站是 SPA,则您的现场数据可能会揭示导致大量渲染工作的交互。
无论原因是什么,以下是一些您可以探索的潜在原因,以帮助使事情重回正轨。
尽可能从服务器提供更多 HTML
如前所述,浏览器默认情况下以非常高性能的方式处理来自服务器的 HTML。它将分解 HTML 的解析和渲染,以避免长任务,并优化主线程的总时间量。这会导致更低的总阻塞时间 (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 来说就像 Next.js 对于 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 渲染引入了许多潜在的性能问题,在许多情况下这些问题是可以避免的。但是,由于每个网站的个别要求,并非 100% 的时间都可以完全避免。为了缓解可能因过度客户端渲染而导致的长任务,请确保尽可能从服务器发送网站的大部分 HTML,对于必须在客户端上渲染的 HTML,请尽可能保持 DOM 大小尽可能小,并考虑替代架构来加速 HTML 向客户端的交付,同时利用浏览器为从服务器加载的 HTML 提供的增量解析和渲染。
如果您可以将网站的客户端渲染降至最低,您不仅可以提高网站的 INP,还可以提高其他指标,例如 LCP、TBT,甚至在某些情况下还可以提高 TTFB。
英雄图片来自 Unsplash,作者 Maik Jonietz。