使用 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 时,可以执行其他工作,包括向用户呈现网页所需的增量渲染工作,以及处理网页关键启动期间可能发生的用户互动。这种方法可为网页带来更高的 Interaction to Next Paint (INP) 分数。
从中可以看出,从服务器流式传输 HTML 时,您可以免费增量解析和呈现 HTML,并自动让出主线程。而客户端呈现则不会出现这种情况。
浏览器如何呈现 JavaScript 提供的 HTML
虽然对网页的每次导航请求都需要服务器提供一定数量的 HTML,但有些网站会使用 SPA 模式。这种方法通常涉及由服务器提供最少的初始 HTML 载荷,但客户端会使用从服务器提取的数据组装的 HTML 填充网页的主要内容区域。后续导航(在本例中有时称为“软导航”)完全由 JavaScript 处理,以便使用新的 HTML 填充页面。
在通过 JavaScript 将 HTML 动态添加到 DOM 的更有限的情况下,非 SPA 中也可能会发生客户端呈现。
通过 JavaScript 创建 HTML 或向 DOM 添加内容有几种常见方法:
- 借助
innerHTML
属性,您可以通过字符串设置现有元素的内容,浏览器会将该字符串解析为 DOM。 - 借助
document.createElement
方法,您可以创建要添加到 DOM 的新元素,而无需使用任何浏览器 HTML 解析。 - 借助
document.write
方法,您可以将 HTML 写入文档(浏览器会对其进行解析,就像在方法 1 中一样)。不过,由于多种原因,强烈建议不要使用document.write
。

通过客户端 JavaScript 创建 HTML/DOM 可能会产生严重后果:
- 与服务器在响应导航请求时流式传输的 HTML 不同,客户端上的 JavaScript 任务不会自动分块,这可能会导致长任务阻塞主线程。这意味着,如果您在客户端一次创建的 HTML/DOM 过多,网页的 INP 可能会受到不利影响。
- 如果在启动期间在客户端上创建了 HTML,浏览器预加载扫描器不会发现其中引用的资源。这肯定会对网页的 Largest Contentful Paint (LCP) 产生负面影响。虽然这不是运行时性能问题(而是提取重要资源时出现的网络延迟问题),但您肯定不希望因忽略这项基本浏览器性能优化而影响网站的 LCP。
如何应对客户端渲染对性能的影响
如果您的网站严重依赖于客户端呈现,并且您发现字段数据中的 INP 值较差,可能会怀疑客户端呈现是否与此问题有关。例如,如果您的网站是 SPA,您的字段数据可能会揭示导致大量渲染工作的互动。
无论原因如何,您可以探索以下一些潜在原因,以帮助一切恢复正常。
从服务器提供尽可能多的 HTML
如前所述,浏览器默认以高性能的方式处理来自服务器的 HTML。它会以避免长时间运行的任务的方式拆分 HTML 的解析和呈现,并优化主线程的总时间量。这会降低 Total Blocking Time (TBT),而 TBT 与 INP 密切相关。
您可能依赖于前端框架来构建网站。如果是,您需要确保在服务器上呈现组件 HTML。这将限制您的网站需要的初始客户端渲染量,并有望带来更好的体验。
- 对于 React,您需要使用 Server DOM API 在服务器上呈现 HTML。但请注意:传统的服务器端渲染方法使用同步方法,这可能会导致收到第一个字节的时间 (TTFB) 以及后续指标(例如 First Contentful Paint [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。
考虑使用在线媒体服务 worker 架构
这是一种高级技术,可能并不适用于所有用例,但可以将您的 MPA 网站变成一种让用户在从一个网页导航到下一个网页时感觉瞬间加载的网站。您可以使用 Service Worker 在 CacheStorage
中预缓存网站的静态部分,同时使用 ReadableStream
API 从服务器提取网页的其余 HTML。
成功使用此技术后,您无需在客户端上创建 HTML,但从缓存中即时加载内容部分会给人一种网站加载速度很快的印象。使用这种方法的网站看起来几乎就像 SPA,但没有客户端呈现的缺点。它还会减少您从服务器请求的 HTML 量。
简而言之,流式服务工作器架构不会替换浏览器的内置导航逻辑,而是会对其进行补充。如需详细了解如何使用 Workbox 实现此目的,请参阅使用流加快多页应用的速度。
总结
网站接收和呈现 HTML 的方式会影响性能。如果您依赖服务器发送网站正常运行所需的所有(或大部分)HTML,则可以免费获得很多好处:增量解析和呈现,以及自动让出主线程以避免长时间运行的任务。
客户端 HTML 呈现会引入许多潜在的性能问题,但在许多情况下,这些问题是可以避免的。不过,由于每个网站的要求不同,因此并非 100% 的时间都能完全避免这种情况。为了减少因客户端呈现过多而可能导致的长时间任务,请确保尽可能从服务器发送尽可能多的网站 HTML,尽可能缩小必须在客户端呈现的 HTML 的 DOM 大小,并考虑采用其他架构来加快向客户端传送 HTML 的速度,同时利用浏览器为从服务器加载的 HTML 提供增量解析和呈现。
如果您能尽可能减少网站的客户端呈现,不仅能改善网站的 INP,还能改善 LCP、TBT 等其他指标,在某些情况下甚至可能还能改善 TTFB。
主打图片来自 Unsplash,摄影师:Maik Jonietz。