优化加载顺序

注意:本文深受 Chrome 中 Aurora 团队的见解的影响,尤其是 Shubhie Panicker,他一直在研究最佳加载顺序。

在每个成功的网页加载中,一些关键组件和资源会在适当的时候变为可用,从而为您提供流畅的加载体验。
这可确保用户认为应用程序的性能非常出色。这种出色的用户体验通常也应该转化为通过 Core Web Vitals。

用于衡量性能的关键指标,例如 First Content Paint、Largest Contentful Paint、First Input Delay 等,直接取决于关键资源的加载顺序。
例如,如果没有加载像英雄图像这样的关键资源,页面就不能有它的 LCP。
这篇文章讲的是资源的加载顺序和Web Vitals之间的关系。我们的目标是提供有关如何优化加载顺序以获得更好的 Web 重要信息的明确指导。

在建立一个理想的加载顺序之前,首先尝试理解为什么很难获得正确的加载顺序。


为什么难以实现最佳加载?

我们有独特的机会为我们合作伙伴的许多网站进行性能分析。 发现了多个类似的问题,这些问题困扰着不同合作伙伴网站的页面高效加载。

开发人员的期望与浏览器如何优先考虑页面上的资源之间通常存在重大差距。
这通常会导致次优的性能得分。 我们进一步分析以发现造成这种差距的原因,以下几点总结了我们分析的本质。

次优测序

Web Vitals 优化不仅需要很好地理解每个指标的含义,还需要它们出现的顺序以及它们与不同关键资源的关系。
FCP 发生在 LCP 之前,而 LCP 发生在 FID 之前。
因此,实现 FCP 所需的资源应优先于 LCP 所需的资源,其次是 FID 所需的资源。

资源通常没有以正确的顺序排序和流水线化。
这可能是因为开发人员没有意识到指标对资源负载的依赖性。 因此,有时无法在正确的时间使用相关资源来触发相应的指标。

例子:

  • a) 到 FCP 触发时,英雄图像应该可用于触发 LCP。

  • b) 到 LCP 触发时,JavaScript (JS) 应该被下载、解析并准备好(或已经执行)以解除阻止交互 (FID)。

网络/CPU 利用率

资源也没有适当地流水线化以确保充分利用 CPU 和网络。
当进程受网络限制时,这会导致 CPU 上的“死区时间”,反之亦然。

一个很好的例子是可以同时或顺序下载的脚本。
由于在并发下载期间带宽被划分,因此下载所有脚本的总时间对于顺序下载和并发下载是相同的。
如果您同时下载脚本,则在下载期间 CPU 未得到充分利用。
但是,如果您按顺序下载脚本,则 CPU 可以在下载后立即开始处理第一个脚本。 这会导致更好的 CPU 和网络利用率。

第三方 (3P) 产品

通常需要 3P 库来向网站添加通用特性和功能。
第三方包括广告、分析、社交小部件、实时聊天和其他支持网站的嵌入。 第三方库带有自己的 JavaScript、图像、字体等

3P 产品通常没有动力优化和支持消费者网站的加载性能。
它们可能具有沉重的 JavaScript 执行成本,从而延迟交互,或妨碍下载其他关键资源。

包含 3P 产品的开发人员可能更关注他们在功能方面增加的价值,而不是性能影响。
因此,有时会随意添加 3P 资源,而没有充分考虑它如何适应整个加载顺序。 这使他们难以控制和安排。

平台怪癖

浏览器在对请求进行优先级排序和实现提示的方式上可能有所不同。
如果您对平台及其怪癖有深入的了解,优化会更容易。 特定浏览器的特定行为使得难以一致地实现所需的加载顺序。

这方面的一个例子是铬平台上的预加载错误。 Preload (<link rel=preload>) 指令可以用来告诉浏览器尽快下载关键资源。
仅当您确定将在当前页面上使用该资源时才应使用它。
Chromium 中的错误导致它的行为使得通过 <link rel=preload> 发出的请求始终在预加载扫描器看到的其他请求之前启动,
即使这些请求具有更高的优先级。 诸如此类的问题会影响优化计划。

HTTP2 优先级

协议本身并没有提供很多用于调整资源顺序和优先级的选项或旋钮。

即使可以使用更好的优先级原语,HTTP2 优先级也存在一些潜在的问题,这使得优化排序变得困难。
主要是,我们无法预测服务器或 CDN 将按什么顺序优先处理对单个资源的请求。
一些 CDN 重新确定请求的优先级,而其他 CDN 实施部分、有缺陷或没有优先级。

资源级优化

有效的排序需要正在排序的资源得到最佳服务,以便它们能够快速加载。
关键的 CSS 应该被内联,图像的大小应该正确,JS 应该是代码拆分和增量交付的。

该框架本身缺乏允许代码拆分和增量服务 JS 和数据的结构。 用户必须依靠以下之一来拆分大块的 1P JS

  • Modern React (Suspense / Concurrent mode / Data Fetching) - 这仍然仅可用于实验

  • 使用动态导入的延迟加载 - 这不直观,开发人员需要手动确定拆分代码的边界。

在代码拆分时,由于粒度与性能的权衡,开发人员需要实现块的正确粒度。

更高的粒度是可取的,因为它

  • 最小化单个路由和后续用户交互所需的 JS

  • 允许缓存常见的依赖项。 这确保库中的更改不需要重新获取整个包。

同时,代码拆分时的粒度太大可能很糟糕,因为太多的小块会降低单个块的压缩率并影响浏览器性能。

资源优化还需要消除死代码或未使用的代码。 不必要的或过时的 JS 可能经常被运送到现代浏览器,这会对性能产生负面影响。 现代浏览器不需要将 JS 转译为 ES5 并与 polyfill 捆绑在一起。 库和 npm 包通常不以 ES 模块格式发布。 这使得打包者很难进行摇树和优化

您可能已经注意到,这些问题并不限于一组特定的资源或平台。 要解决这些问题,需要了解整个技术堆栈以及如何合并不同的资源以实现最佳指标。 在我们定义整体优化策略之前,让我们先看看单个资源需求是如何破坏我们的目标的。


有关资源的更多信息 - 关系、约束和优先级


知识点