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

了解 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 可以更快地处理预提取的脚本。这可能会影响网页的 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 属性可帮助浏览器设置正确的标头,并确定资源是否已在缓存中。此属性的示例值包括:documentscriptstylefontimageothers

您还可以通过 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) });
});

无需等待“提交”事件来加载此功能,那么您可以预提取该资源,以便在用户提交表单时提高在缓存中提供该资源的几率。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,则 Quicklink 和 Guess.js 都使用 Network Information API 来避免预提取。

后台预提取

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

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

预先抓取在“最低”优先级,这样预提取的资源就不会与当前网页中所需的资源竞争带宽。

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

总结

使用 prefetch 可以极大地缩短今后导航的加载时间,甚至能让网页看起来像是即时加载。prefetch 在现代浏览器中得到广泛支持,这使其成为改进许多用户的导航体验的很有吸引力的技术。此方法需要加载可能用不到的额外字节,因此使用时要小心;最好只在必要时才使用较快的网络。