使用 JavaScript 呈现 HTML 与呈现服务器发送的 HTML 不同,这可能会影响性能。请参阅本指南,了解这两者之间的区别,以及您可以采取哪些措施来保持网站的渲染性能,尤其是在涉及互动时。
对于使用浏览器内置导航逻辑(有时称为“传统网页加载”或“硬导航”)的网站,浏览器默认情况下可以很好地解析和呈现 HTML。此类网站有时称为多页应用 (MPA)。
不过,开发者可以绕过浏览器默认设置,以满足其应用需求。对于使用单页应用 (SPA) 模式的网站来说,情况确实如此,因为它们会使用 JavaScript 在客户端上动态创建 HTML/DOM 的大部分内容。这种设计模式称为客户端渲染,如果涉及的工作过多,可能会影响网站的 Interaction to Next Paint (INP)。
本指南将帮助您权衡使用服务器发送到浏览器的 HTML 与使用 JavaScript 在客户端创建 HTML 之间的差异,以及后者如何在关键时刻导致较高的互动延迟时间。
浏览器如何呈现服务器提供的 HTML
传统网页加载中使用的导航模式涉及在每次导航时从服务器接收 HTML。如果您在浏览器的地址栏中输入网址或点击 MPA 中的链接,系统会按顺序执行以下操作:
- 浏览器会针对所提供的网址发送导航请求。
- 服务器以块的形式返回 HTML。
最后一步至关重要。它也是服务器/浏览器交换中最基本的性能优化之一,称为流式传输。如果服务器可以尽快开始发送 HTML,并且浏览器无需等待整个响应到达,则浏览器可以在 HTML 到达时分块处理 HTML。

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

通过客户端 JavaScript 创建 HTML/DOM 可能会产生严重后果:
- 与服务器为响应导航请求而流式传输的 HTML 不同,客户端上的 JavaScript 任务不会自动分块,这可能会导致长时间运行的任务阻塞主线程。这意味着,如果您在客户端上一次性创建过多的 HTML/DOM,网页的 INP 可能会受到负面影响。
- 如果在启动期间在客户端上创建 HTML,则其中引用的资源将不会被浏览器预加载扫描器发现。这肯定会对网页的 Largest Contentful Paint (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(相当于 React 的 Next.js)尽可能将许多最佳实践嵌入到您的 Svelte 项目中,因此您可以避免仅使用 Svelte 的项目中的潜在陷阱。
限制在客户端上创建的 DOM 节点数量
当 DOM 较大时,渲染它们所需的处理量往往会增加。无论您的网站是成熟的 SPA,还是通过 MPA 在现有 DOM 中注入新节点,都应尽可能缩小这些 DOM。这有助于减少在客户端渲染期间显示该 HTML 所需的工作量,从而有助于降低网站的 INP。
考虑使用流式 Service Worker 架构
这是一项高级技术,可能无法轻松应用于所有使用情形,但它可以将 MPA 变成一个网站,让用户在从一个网页导航到另一个网页时感觉网页是即时加载的。您可以使用 Service Worker 在 CacheStorage
中预缓存网站的静态部分,同时使用 ReadableStream
API 从服务器提取网页的其余 HTML。
如果您成功使用此技术,则不会在客户端上创建 HTML,但从缓存中即时加载部分内容会给用户留下网站加载速度快的印象。采用这种方法的网站几乎可以像 SPA 一样流畅,但不会出现客户端渲染的缺点。它还可以减少您从服务器请求的 HTML 量。
简而言之,流式传输服务工作器架构不会取代浏览器的内置导航逻辑,而是添加到其中。如需详细了解如何使用 Workbox 实现此目的,请参阅使用流更快地加载多页应用。
总结
网站接收和呈现 HTML 的方式会影响性能。如果您依赖服务器来发送网站运行所需的所有(或大部分)HTML,那么您将免费获得许多好处:增量解析和渲染,以及自动将控制权让给主线程以避免长时间运行的任务。
客户端 HTML 渲染会带来许多潜在的性能问题,但在许多情况下可以避免这些问题。不过,由于每个网站的要求各不相同,因此无法完全避免这种情况。为了缓解过度客户端渲染可能导致的长任务,请确保尽可能从服务器发送网站的 HTML,对于必须在客户端上渲染的 HTML,请尽可能减小 DOM 大小,并考虑使用替代架构来加快向客户端传送 HTML 的速度,同时利用浏览器为从服务器加载的 HTML 提供的增量解析和渲染功能。
如果您能尽可能减少网站的客户端渲染,不仅可以改善网站的 INP,还可以改善 LCP、TBT 等其他指标,甚至在某些情况下还可以改善 TTFB。
来自 Unsplash 的主打图片,由 Maik Jonietz 拍摄。