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

了解 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 元素是使用 Web 字体的文本块,则该元素的 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) });
});

您可以预取该资源,以便在用户提交表单时提高在缓存中提供该资源的几率,而不是等待“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,quicklink 和 Guess.js 都会使用 Network Information API 来避免预加载。

预提取的内部机制

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

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

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

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

总结

使用 prefetch 可以显著缩短未来导航的加载时间,甚至让网页看起来像是即时加载的。prefetch 在现代浏览器中得到广泛支持,因此是一项有吸引力的技术,可为许多用户改善导航体验。此技术需要加载可能不会使用的额外字节,因此请谨慎使用;仅在必要时使用,最好仅在高速网络上使用。