预加载关键资源,以提高加载速度

当您打开网页时,浏览器会向服务器请求 HTML 文档,解析其内容,然后针对任何引用的资源提交单独的请求。作为开发者,您已经了解自己的网页需要的所有资源,以及其中哪些资源最为重要。您可以利用这些信息提前请求关键资源,并加快加载速度。本文介绍如何使用 <link rel="preload"> 实现这一点。

预加载的工作原理

预加载最适合通常会被浏览器发现的资源。

Chrome DevTools Network 面板的屏幕截图。
在本示例中,使用 @font-face 规则在样式表中定义 Pacifico 字体。只有在下载和解析完样式表之后,浏览器才会加载字体文件。

通过预加载特定资源,即可告知浏览器您希望比浏览器更早地提取它,因为您确定它对当前网页很重要。

应用预加载后的 Chrome DevTools Network 面板的屏幕截图。
在本示例中,系统预加载了 Pacifico 字体,因此下载与样式表并行进行。

关键请求链表示浏览器提取优先资源的顺序。Lighthouse 会将位于此链第三级的资产标识为后期发现。您可以使用预加载密钥请求审核来确定要预加载的资源。

Lighthouse 的预加载关键请求审核。

您可以通过向 HTML 文档标头添加带有 rel="preload"<link> 标记来预加载资源:

<link rel="preload" as="script" href="critical.js">

浏览器会缓存预加载的资源,以便在需要时立即使用。(它不会执行脚本或应用样式表。)

系统会根据浏览器认为合适的方式执行资源提示,例如 preconnectprefetch。另一方面,preload 对浏览器而言是必需的。现代浏览器已经非常擅长确定资源的优先级,因此,请务必谨慎使用 preload,并且仅预加载最重要的资源。

未使用的预加载项会在 load 事件发生大约 3 秒后在 Chrome 中触发控制台警告。

关于未使用的预加载资源的 Chrome 开发者工具控制台警告。

用例

预加载在 CSS 中定义的资源

在浏览器下载并解析 CSS 文件之前,系统不会发现使用 @font-face 规则定义的字体或在 CSS 文件中定义的背景图片。预加载这些资源可确保在下载 CSS 文件之前提取这些资源。

预加载 CSS 文件

如果您使用的是关键 CSS 方法,则可以将 CSS 拆分为两部分。呈现首屏内容所需的关键 CSS 内嵌在文档的 <head> 中,非关键 CSS 通常会使用 JavaScript 进行延迟加载。如果在加载非关键 CSS 之前等待 JavaScript 执行,可能会导致用户滚动页面时出现延迟,因此最好使用 <link rel="preload"> 尽早启动下载。

预加载 JavaScript 文件

由于浏览器不会执行预加载的文件,因此预加载有助于将提取与执行操作分离开来,从而提高可交互时间等指标。如果您拆分 JavaScript 软件包,并且仅预加载关键分块,则预加载的效果最佳。

如何实现 rel=preload

实现 preload 的最简单方法是向文档的 <head> 添加 <link> 标记:

<head>
  <link rel="preload" as="script" href="critical.js">
</head>

提供 as 属性有助于浏览器根据预提取资源的类型设置其优先级、设置正确的标头,以及确定该资源是否已存在于缓存中。此属性接受的值包括:scriptstylefontimageothers

某些类型的资源(例如字体)以匿名模式加载。对于这些元素,您必须使用 preload 设置 crossorigin 属性:

<link rel="preload" href="ComicSans.woff2" as="font" type="font/woff2" crossorigin>

<link> 元素还接受 type 属性,该属性包含关联资源的 MIME 类型。浏览器使用 type 属性的值来确保仅在其文件类型受支持时预加载资源。如果浏览器不支持指定的资源类型,就会忽略 <link rel="preload">

您还可以通过 Link HTTP 标头预加载任何类型的资源:

Link: </css/style.css>; rel="preload"; as="style"

在 HTTP 标头中指定 preload 的一个好处是,浏览器无需解析文档即可发现文档,而这种做法在某些情况下可以带来细微的改进。

使用 webpack 预加载 JavaScript 模块

如果您使用的模块打包器会创建应用的 build 文件,则需要检查它是否支持注入预加载标记。在 webpack 4.6.0 或更高版本中,可通过在 import() 中使用魔法注释来支持预加载:

import(_/* webpackPreload: true */_ "CriticalChunk")

如果您使用的是旧版 Webpack,请使用第三方插件,例如 preload-webpack-plugin

预加载对 Core Web Vitals 的影响

预加载是一项强大的性能优化,会对加载速度产生影响。此类优化可能会导致您网站的核心网页指标发生变化,因此请务必注意此类优化。

Largest Contentful Paint (LCP)

对于字体和图片而言,预加载会对 Largest Contentful Paint (LCP) 产生极大影响,因为图片和文本节点都是 LCP 候选项。以网页字体呈现的主打图片和大段文字可显著受益于精心放置的预加载提示,因此在有机会更快地向用户提供这些重要内容时,应使用此提示。

不过,涉及预加载和其他优化时,您需要小心谨慎!尤其要避免预加载过多资源。如果优先考虑的资源过多,实际上根本不会分配资源。过多预加载提示的影响对慢速网络造成的负面影响尤其严重,因为带宽争用更明显。

而应专注于您知道适当部署的预加载会受益的一些高价值资源。预加载字体时,请确保以 WOFF 2.0 格式提供字体,以尽可能缩短资源加载时间。由于 WOFF 2.0 具有出色的浏览器支持,因此如果 LCP 候选版本是文本节点,使用 WOFF 1.0 或 TrueType (TTF) 等较旧的格式会延迟您的 LCP。

对于 LCP 和 JavaScript,您需要确保从服务器发送完整的标记,以便浏览器的预加载扫描器正常工作。如果您提供的体验完全依靠 JavaScript 来呈现标记,并且无法发送服务器呈现的 HTML,那么采用以下做法会大有好处:让浏览器预加载扫描器无法进行以下工作,并预加载只有在 JavaScript 完成加载和执行后才能发现的资源。

Cumulative Layout Shift (CLS)

在涉及网络字体的情况下,Cumulative Layout Shift (CLS) 是一项特别重要的指标,CLS 会与使用 font-display CSS 属性管理字体加载方式的网络字体产生明显的相互作用。为了最大限度地减少与网页字体相关的布局偏移,请考虑以下策略:

  1. 在为 font-display 使用默认 block 值时预加载字体。这是一种微妙的平衡。如果不提供回退字体,就阻止显示字体可能会被视为用户体验问题。一方面,使用 font-display: block; 加载字体可消除与网页字体相关的布局偏移。另一方面,如果这些网页字体对于用户体验至关重要,您仍然希望尽快加载它们。将预加载与 font-display: block; 结合使用或许是一种可接受的折衷做法。
  2. 在使用 font-displayfallback 值时预加载字体。fallback 是介于 swapblock 之间的折衷值,因为它的阻止期非常短。
  3. font-display 使用 optional 值,而无需预加载。如果某种网页字体对用户体验而言并不是很重要,但仍用于呈现大量网页文本,请考虑使用 optional 值。在不利的情况下,optional 会在后台加载字体以供下次导航使用时,以后备字体显示页面文本。这些条件下的最终结果是改善了 CLS,因为系统字体会立即渲染,而后续页面加载将立即加载字体,而不会发生布局偏移。

当涉及网络字体时,CLS 是一个很难优化的指标。与往常一样,在实验室中进行实验,但请相信您的实测数据,以确定您的字体加载策略是提升还是降低 CLS。

Interaction to Next Paint (INP)

Interaction to Next Paint 指标,用于衡量对用户输入的响应能力。由于网络上的大部分互动都由 JavaScript 驱动,因此,预加载为重要互动提供支持的 JavaScript 可能有助于降低网页的 INP。但请注意,如果在启动期间预加载过多 JavaScript,如果太多资源争用带宽,可能会产生意外的负面影响。

此外,在进行代码拆分时也要小心谨慎。代码拆分是减少启动期间加载的 JavaScript 量的绝佳优化措施,但是如果互动依赖于在互动开始时加载的 JavaScript,则可能会延迟互动。为了弥补这一点,您需要检查用户的 intent,并在互动发生之前注入必要的 JavaScript 块的预加载项。例如,当表单中的任何字段获得焦点时,预加载验证表单内容所需的 JavaScript 即可。

总结

若要提高网页速度,请预加载浏览器较晚才发现的重要资源。预加载所有内容都会适得其反,因此请谨慎使用 preload衡量实际影响