一、Web Vitals

Lighthouse 的性能检测是基于 Web Vitals 指标来测量页面的性能。

Web Vitals,即 Google 给的定义是一个良好网站的基本指标(Essential metrics for a healthy site)。在过去要去衡量一个高质量网站,需要的指标太多,且有些指标计算很复杂,所以,Google 推出 Web Vitals 就是为了简化这个过程,用户仅仅需要关注 Web Vitals 即可。

Web Vitals 给出的指标:

  • FCP(First Contentful Paint):测量应用程序在初次访问期间需要渲染 DOM 中的第一个元素的时间。
  • SI(Speed Index):首屏展现平均值,衡量页面内容填充的速度(越低越好)。它特别适用于比较不同页面之间的差别衡量应用程序渲染内容的速度。Lighthouse 通过捕获在浏览器中加载页面的视频并检查每个视频帧(在启用视频捕获的测试中,每秒10帧)来完成的。
  • LCP:显示最大内容元素所需时间。计算网页可视范围内最大的内容元件需花多少时间载入。这项指标的意义是:网页上的主要内容需花多少时间才会被使用者看到,相当于网页给人的第一印象。当页面被载入时,Google 会抓取页面中最大元素的载入时间作为 LCP,而且 LCP 会随着载入的内容越来越多而改变,直到页面完全载入后,最大元素即被确定为「真正的」LCP。
  • TTI(交互时间):测量应用程序何时准备好与用户交互。主要是通过跟踪耗时较长的任务来确定,设置 PerformanceObserver 观察类型为 longtask 的条目,然后可以根据耗时较长的条目的 startTime 和duration,来大致确认页面处于 idle 的时间,从而确定 TTI 指标。
  • TBT(总阻塞时间):衡量我们应用程序中“长任务”(耗时超过 50 毫秒的任务)的影响。测量First Contentful Paint 首次内容绘制 (FCP)与Time to Interactive 可交互时间 (TTI)之间的总时间,这期间,主线程被阻塞的时间过长,无法作出输入响应。那它是如何工作的?让我们看一下这个例子:

假设我们的应用程序包含 20 个任务:

  • 10 个任务每个需要 40 毫秒
  • 10 个任务每个需要 60 毫秒

TBT 只关心 60 毫秒的任务(更准确地说,是 50 毫秒阈值与值本身之间的差异),所以我们的最终结果将是:

AMOUNT x (VALUE – THRESHOLD) = RESULT

10 x (60ms – 50ms) = 100ms

  • CLS(Cumulative Layout Shift):初始视口中所有意想不到的布局变化。值是根据“不稳定”元素在帧之间移动的距离计算的。累计布局位移,用于衡量视觉稳定性,谷歌要求页面的CLS最好保持小于0.1。它是一个重要的、以用户为中心的衡量视觉稳定性的指标,因为它有助于量化用户体验意外布局位移的频率,低 CLS 有助于确保页面令人愉快。

二、开始修复

1. 修复字体文件以提高 Lighthouse 分数

为什么字体会影响你的灯塔分数?这是因为它们的使用方式不仅会影响页面速度(不同的字体有不同的大小),而且会对查看者在不查看页面时的查看方式产生深远影响。以下是一些需要注意的事项:

  • 自托管:避免从无法控制的外部服务加载字体文件。只要有可能,应该自行托管字体文件以避免更长的 HTTP 请求,或者使用带有缓存的 CDN 托管。
  • 字体扩展:字体扩展对文件的最终大小有很大的影响。如果你选择的字体带有不同的扩展选项,则应始终选择最轻的 WOFF2。

字体子集:一些字体有更小的变体,称为“子集”。 它们包含更少的字形,这进一步减小了文件的大小。例如,某些字体具有仅包含拉丁字母和字符的“拉丁”子集。

  • 可变字体:可以将字体的多种变体合并到单个文件中,因此我们可以仅加载一个通常小于所有文件组合的文件,而不是加载具有不同变体的“X”数量的不同文件。

假设我们想使用字体的所有变体(在这个例子中是 9 个文件)。我们将之前的结果乘以 9,并将其大小与单个可变字体文件进行比较。

2. 脚本

脚本也会影响应用的性能——尤其是当它们在不需要的地方出现瓶颈或占用宝贵的加载时间时。处理这方面的方法:

  • 异步加载。始终使用 async 或 defer 延迟加载第三方脚本,以防止阻塞应用程序的主线程。你还可以使用 next/script 来设置脚本的优先级。
  • 资源提示。明智地使用资源提示来进一步减少加载脚本所需的时间。
  • 跟踪代码管理器。考虑将第三方脚本的加载委托给代码管理器,你可以更好地控制脚本加载的顺序和脚本的数量。

3. 样式

  • CSS over CSS-in-JS 解决方案。在样式方面,您可能需要考虑一种更“老式”的方式。因为在 SSR 应用程序中,我们不想用更多的 JavaScript 占用主线程。这就是为什么 CSS-in-JS 解决方案不是最适合 Next.js 应用程序的原因。

此外,Next.js 已经内置了很多 CSS 优化方案,比如类名和样式缩小、sass 支持、配置 postcss。

  • 字体显示。为了避免 FOUT(无样式文本的闪烁)或看到空白屏幕,应该始终通过使用字体上的 font-display 属性来控制字体的加载。

4. Bundles

分析 Bundles 包可以很好地发现应用的 Chunk 的数量和大小。

  • Bundle-wizard。它是 @next/bundle-analyzer 的一个很不错的替代品,它允许我们检查我们的应用程序包。在我看来,它比其他工具有 3 大优势:

(1)它有一个更好的用户界面

(2)它提供了 chunk 的覆盖范围

(3)它可以在构建期间在任何已部署的应用程序上运行

  • chunk 拆分。减少 bundles 包大小的一个好方法是将它们分成更小的部分。我们的应用程序更容易加载多个较小的块而不是几个大块。幸运的是,webpack 确实允许我们拆分合并的块。此外,我们可以控制模块的优先级。
  • 删除重复的模块。有时在 monorepo 架构中工作时,我们可能会得到多次捆绑的包。同样,webpack config 带有一个可以合并我们重复的块的属性。

5. CLS

每当可见元素将其位置从一个渲染帧更改为下一帧时,就会发生布局转换。

最常见的影响CLS的分数的有:

  • 未指定尺寸的图片
  • 未指定尺寸的广告、嵌入元素、iframe
  • 动态插入内容
  • 自定义字体(引发FOIT/FOUT)
  • 在更新 DOM 之前等待网络响应的操作

因此,需要为动态内容保留空间。为了防止任何意外的布局变化,我们应该始终为尚未渲染的内容保留空间。

有很多很棒的方法,比如骨架加载,它模仿给定组件的一般外观,包括它的宽度和高度。这样,我们将保留确切的空间,从而消除 CLS。

但有时,我们不必使用任何花哨的东西。我们可以只插入一个空的占位符框,这将确保用户没有不愉快的体验。

(1)图像

图像可能是最臭名昭著的页面速度的恶棍。我们有更多的技巧可以让这个问题成为不是问题:

  • 使用新一代文件扩展名。考虑以 webp 或 jpeg2000 文件扩展名提供图像。它比传统的 jpeg 或 png 轻得多,而且没有明显的质量损失。 有许多库可以在上传过程中将图像转换为 webp,因此请随意使用它们。但请始终记住,某些较旧的浏览器可能不支持该扩展,因此请准备适用格式的后备版本。
  • 尺寸变体。Lighthouse 确实建议为提供不同变体的图像。像Sharp 这样的库允许我们生成同一张图像的多种尺寸。要显示它们,我们可以使用 标签或 img srcSet 属性。
  • 延迟加。总是延迟加载视口之外的图像。这样,我们可以在第一次访问我们的页面时节省时间。为此,我们可以在 img 标签上使用 loading=”lazy”属性。
  • 预加载。考虑预加载首屏的图像,尤其是 LCP 元素。预加载“告诉”浏览器需要比正常情况更早地获取内容。
  • 使用 Next/image 组件。Next/Image 组件,它将通过转换为 webp、调整大小、延迟加载和预加载 API 为我们优化图像。

6. Javascript

有时,在 SEO 性能方面,JavaScript 可能会成为反派。为了提高应用程序的分数,我们可以避免一些常见错误:

  • 代码拆分(动态导入)。代码拆分允许我们延迟加载一些代码,因此它减少了我们应用程序的主线程必须做的工作量。 Next/dynamic 是一个很好的代码拆分工具。使用简单的 API,我们可以将组件拆分为单独的块,这些块将按需加载。我们还可以控制组件是否应该在服务器端呈现。
  • 树摇。避免直接使用 export default 导出文件, 而是导出需要用到的模块。例如:export const function test() {}

7. 如何保持高性能

如果我们已经达到了让我们满意的性能水平,那么随着时间的推移将其保持在同一水平会很好。有一些工具可以帮助我们做到这一点:

  • Bundle-wizard。在我们的应用程序增长时不时运行此工具是一个很好的做法,以确保包大小保持较小,并且我们不会遇到任何意外的块问题。
  • Webpack 性能提示。来自 webpack 的性能提示是我们运行 bundle-wizard 的一个很好的指标。它们很容易配置,当任何应用程序块超过大小限制时,可以在构建期间抛出警告或错误。
  • PageSpeed Insights / Lighthouse。当然,我们衡量应用程序性能的主要工具是 Lighthouse。我们可以通过 Chrome 浏览器中的开发工具运行它,也可以通过 PSI 网站运行它。
  • WebPageTest。是 Lighthouse 的一个很酷的替代品。它也为我们测量了 Web Vitals,但以更易于访问的方式为我们提供了更多信息。例如,我们可以看到任务的瀑布或渲染过程的幻灯片。
  • Lighthouse-CI。使用 Lighthouse-CI,我们可以为特定指标或 CI 流程的整体性能得分设置测试。这样,我们可以随着应用程序的增长持续测量页面速度。
  • 衡量真实用户的表现。这是对已经投入生产并拥有一些活跃用户的应用程序的提示。衡量真实用户的表现是了解我们的弱点在哪里的关键。

像 Lighthouse 或 WebPageTest 这样的工具有时会产生误导,因为它们总是在稳定的互联网连接、最新版本的 Chrome 等环境下工作……而对于我们的最终用户而言,情况并非总是如此。通常,用户在给定页面上的表现可能比 Lighthouse 建议的要差得多。

三、结尾

Web App 的性能不是修复一次就可以完成的。 它更像是一个随着应用程序的增长而不断检查、分析和改进应用程序的过程。幸运的是,我们可以而且应该尽可能地自动化这个过程。