预提取资源以加快后续导航速度

了解 rel=prefetch 资源提示及其使用方法。

研究表明,加载时间越短,转化率就越高,用户体验也越好。如果您深入了解用户如何浏览您的网站以及他们接下来可能会访问哪些网页,就可以提前下载这些网页的资源,从而缩短后续导航的加载时间。

本指南介绍了如何使用<link rel=prefetch>实现预提取资源提示,从而轻松高效地实现预提取。

利用 rel=prefetch 改进导航功能

向网页添加 <link rel=prefetch> 会告知浏览器下载用户将来可能需要的完整网页或某些资源(例如脚本或 CSS 文件):

<link rel="prefetch" href="/articles/" as="document">

展示链接预提取工作原理的图表。

prefetch 提示会为并非立即所需的资源消耗额外的字节,因此需要谨慎应用此技术;仅当您确信用户需要它们时,才预提取资源。请考虑不要在用户的连接速度较慢时进行预提取。您可以使用 Network Information API 进行检测。

有多种方法可以确定要预提取的链接。最简单的方法是预提取当前网页上的第一个链接或前几个链接。此外,还有一些库采用更复杂的方法,本博文后面部分会对此进行说明。

用例

预提取后续网页

在后续页面可预测时预提取 HTML 文档,以便在点击链接时可立即加载页面。

例如,在商品详情页面中,您可以预提取列表中最热门商品的页面。在某些情况下,下一次导航更容易预料到。在购物车页面上,用户访问结账页的可能性通常较高,因此非常适合进行预提取。

虽然预提取资源会消耗额外的带宽,但可以提高大多数性能指标。首字节时间 (TTFB) 通常要低得多,因为文档请求会导致缓存命中。由于 TTFB 较低,因此后续基于时间的指标通常也会降低,包括 Largest Contentful Paint (LCP)First Contentful Paint (FCP)

预提取静态资源

在能够预测到用户可能访问的后续部分时预提取脚本或样式表等静态资源。当这些资源被共享到多个网页时,此功能尤为有用。

例如,Netflix 利用用户在未登录的网页上花费的时间来预提取用户登录后使用的 React。因此,他们将未来导航的可交互时间缩短了 30%

预提取静态资源对性能指标的影响取决于预提取的资源:

  • 预提取图片可以显著缩短 LCP 图片元素的 LCP 时间。
  • 预提取样式表可以同时改善 FCP 和 LCP,因为这样可以省去下载样式表所需的网络时间。由于样式表会阻塞渲染,因此在预提取时,它们可能会降低 LCP。如果后续页面的 LCP 元素是通过 background-image 属性请求的 CSS 背景图片,则系统也会将该图片作为预提取的样式表的独立资源进行预提取。
  • 与在导航期间需要由网络首先提取相比,预提取 JavaScript 可以让预提取脚本的处理更快完成。这会影响响应能力指标,例如 First Input Delay (FID)Interaction to Next Paint (INP)。如果标记是通过 JavaScript 在客户端上呈现的,可以通过减少资源加载延迟时间来改进 LCP,并且可以更早地在客户端呈现包含网页 LCP 元素的标记。
  • 通过预提取当前页面尚未使用的网络字体,可以避免布局偏移。在使用 font-display: swap; 的情况下,系统会消除字体的交换期,从而加快文本渲染并避免布局偏移。如果以后的网页使用预提取的字体,并且该网页的 LCP 元素是使用网络字体的文本块,则该元素的 LCP 也会加快。

预提取按需 JavaScript 块

通过对 JavaScript 软件包进行代码拆分,您可以先仅加载应用的部分,然后延迟加载其余部分。如果您要使用这种方法,则可以将预提取应用于并非立即需要但可能很快就会被请求的路由或组件。

例如,如果您的网页包含的按钮用于打开包含表情符号选择器的对话框,那么您可以将该网页分成三个 JavaScript 区块:主页、对话框和选择器。主屏幕和对话框可以最初加载,而选择器可以按需加载。利用 webpack 等工具,你可以指示浏览器预提取这些按需数据块。

如何实现 rel=prefetch

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

<head>
  ...
  <link rel="prefetch" href="/articles/" as="document">
  ...
</head>

as 属性可帮助浏览器设置正确的标头,并确定资源是否已在缓存中。此属性的示例值包括:documentscriptstylefontimage其他

您也可以通过 Link HTTP 标头启动预提取:

Link: </css/style.css>; rel=prefetch

在 HTTP 标头中指定预提取提示的一个好处是,浏览器无需解析文档即可找到资源提示,这在某些情况下可以提供细微的改进。

使用 webpack 魔法注释预提取 JavaScript 模块

webpack 可用于预提取用户肯定会很快访问或使用的路线或功能对应的脚本。

以下代码段会延迟加载 lodash 库中的排序功能,从而对将通过表单提交的一组数字进行排序:

form.addEventListener("submit", e => {
  e.preventDefault()
  import('lodash.sortby')
    .then(module => module.default)
    .then(sortInput())
    .catch(err => { alert(err) });
});

您可以不等待“submit”事件发生来加载此功能,而是可以预取该资源,以提高在用户提交表单时它出现在缓存中的几率。webpack 允许使用 import() 内的魔法注释

form.addEventListener("submit", e => {
   e.preventDefault()
   import(/* webpackPrefetch: true */ 'lodash.sortby')
         .then(module => module.default)
         .then(sortInput())
         .catch(err => { alert(err) });
});

这会告知 webpack 将 <link rel="prefetch"> 标记注入 HTML 文档中:

<link rel="prefetch" as="script" href="1.bundle.js">

预提取按需分块的性能优势有些微妙,但一般来说,对于依赖于这些按需分块的互动,您应该能够更快做出响应,因为它们将立即可用。根据互动的性质,这可能会给网页的 INP 带来好处。

一般来说,预提取也会计入整体资源优先级。预提取资源时,系统会以尽可能低的优先级执行预提取。因此,任何预提取的资源都不会争用当前页面所需的资源。

您还可以通过在后台使用 prefetch 的库来实现更智能的预提取:

如果用户的网络连接速度较慢或已开启 Save-Data,那么快速链接和 Guess.js 都使用 Network Information API 来避免预提取。

在后台预先抓取

资源提示不是强制性的指令,是否执行以及何时执行由浏览器决定。

您可以在同一网页中多次使用预提取功能。浏览器会将所有提示加入队列,并在每个资源处于空闲状态时请求资源。在 Chrome 中,如果预提取尚未完成,并且用户导航到预期的预提取资源,则浏览器会提取传输中的加载作为导航(其他浏览器供应商可能会采用不同的实现方法)。

预提取时采用“最低”优先级,因此预提取的资源不会与当前页面中所需的资源竞争带宽。

预提取的文件会在 HTTP 缓存内存缓存(具体取决于资源是否可缓存)中存储一段时间,具体时间因浏览器而异。例如,在 Chrome 中,资源会保留大约 5 分钟,之后,系统将应用资源的常规 Cache-Control 规则。

总结

使用 prefetch 可以大幅缩短未来导航的加载时间,甚至让网页看起来像是即时加载。现代浏览器广泛支持 prefetch,这使得它成为改善许多用户的导航体验的一项富有吸引力的技术。这种方法需要加载可能用不到的额外字节,因此使用时要留心;仅在必要时执行,最好只使用速度较快的网络。