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

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

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

本指南介绍了如何使用 <link 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 元素的标记的客户端渲染可以更早发生。
  • 预提取当前网页尚未使用的 Web 字体可以消除布局偏移。在使用 font-display: swap; 的情况下,系统会消除字体的换货期,从而加快文本渲染速度并消除布局偏移。如果未来某个网页使用预提取的字体,并且该网页的 LCP 元素是使用网页字体的文本块,则该元素的 LCP 速度也会加快。

预提取按需 JavaScript 分块

通过代码拆分 JavaScript 软件包,您可以最初只加载应用的部分内容,并延迟加载其余内容。如果您使用此方法,则可以将预提取应用于并非立即需要但可能很快就会被请求的路由或组件。

例如,如果您的网页包含一个按钮,该按钮会打开一个包含表情符号选择器的对话框,您可以将其划分为三个 JavaScript 分块:home、dialog 和 picker。首页和对话框最初可以加载,而选择器则可按需加载。借助 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) });
});

您可以预加载此资源,而不是等待“提交”事件发生以加载此功能,以提高在用户提交表单时该资源在缓存中可用几率。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 在现代浏览器中得到广泛支持,这使其成为改善许多用户的导航体验的很有吸引力的技术。此技术需要加载可能不会使用的额外字节,因此请谨慎使用;仅在必要时使用,最好仅在高速网络上使用。