使用 Fetch Priority API 优化资源加载

Fetch Priority API 指示浏览器资源的相对优先级。它可以实现最佳加载并改进 Core Web Vitals。

浏览器支持

  • Chrome: 102.
  • Edge: 102.
  • Firefox: 132.
  • Safari: 17.2.

来源

当浏览器解析网页并开始发现和下载资源(例如图像、脚本或 CSS)时,它会为这些资源分配一个提取 priority,以便能够以最佳顺序下载它们。资源的优先级通常取决于资源的类型及其在文档中的位置。例如,视口内的图像可能具有 High 优先级,而使用 <head> 中的 <link> 标记的早期加载、渲染阻塞型 CSS 的优先级可能为 Very High。浏览器在分配效果良好的优先级方面做得相当不错,但在所有情况下可能并非最佳。

本页讨论 Fetch Priority API 和 fetchpriority HTML 属性,该属性允许您提示资源的相对优先级(highlow)。Fetch Priority 可以帮助优化 Core Web Vitals。

摘要

Fetch Priority 可以提供帮助的几个关键领域

  • 通过在图像元素上指定 fetchpriority="high" 来提高 LCP 图像的优先级,从而使 LCP 更早发生。
  • 提高 async 脚本的优先级,使用比当前最常见的 hack(为 async 脚本插入 <link rel="preload">)更好的语义。
  • 降低后期 body 脚本的优先级,以便更好地与图像排序。
A filmstrip view comparing two tests of the Google Flights homepage. At the bottom, Fetch Priority is used to boost the priority of the hero image, resulting in a 0.7 second decrease in LCP.
在 Google Flights 的测试中,Fetch Priority 将 Largest Contentful Paint 从 2.6 秒提高到 1.9 秒。

从历史上看,开发人员对资源优先级的控制有限,只能使用 preloadpreconnect。Preload 允许您告知浏览器您希望提前加载的关键资源,在浏览器自然发现它们之前。这对于难以发现的资源尤其有用,例如样式表、背景图像或从脚本加载的资源中包含的字体。Preconnect 有助于预热与跨域服务器的连接,并可以帮助改进诸如 Time to first byte 等指标。当您知道源,但不一定知道将要使用的资源的确切 URL 时,它非常有用。

Fetch Priority 补充了这些 Resource Hints。它是一个基于标记的信号,可通过 fetchpriority 属性获得,开发人员可以使用它来指示特定资源的相对优先级。您还可以通过 JavaScript 和 Fetch API 以及 priority 属性使用这些提示,以影响为数据进行的资源提取的优先级。Fetch Priority 还可以补充 preload。以 Largest Contentful Paint 图像为例,当预加载时,它仍然会获得较低的优先级。如果它被其他早期的低优先级资源推后,则使用 Fetch Priority 可以帮助图像更快地加载。

资源优先级

资源下载顺序取决于浏览器为页面上的每个资源分配的优先级。可能影响优先级计算逻辑的因素包括以下内容

  • 资源的类型,例如 CSS、字体、脚本、图像和第三方资源。
  • 文档中引用资源的位置或顺序。
  • 是否在脚本上使用了 asyncdefer 属性。

下表显示了 Chrome 如何确定大多数资源的优先级和顺序

  在布局阻止阶段加载 在布局阻止阶段一次加载一个
Blink
优先级
VeryHigh High Medium Low VeryLow
DevTools
优先级
最高 High Medium Low 最低
主资源
CSS(早期**) CSS(后期**) CSS(媒体不匹配***)
脚本(早期**或非来自预加载扫描器) 脚本(后期**) 脚本 (async)
字体 字体 (rel=preload)
导入
图像(在视口中) 图像(前 5 张图像 > 10,000 像素2 图像
媒体(视频/音频)
预取
XSL
XHR(同步) XHR/fetch*(异步)

浏览器按照资源被发现的顺序下载具有相同计算优先级的资源。您可以在 Chrome Dev Tools Network 选项卡下检查加载页面时分配给不同资源的优先级。(确保通过右键单击表头并勾选 priority 列来包含该列)。

Network tab of Chrome's DevTools listing a number of font resources. They are all Highest priority.
BBC 新闻详细信息页面上 type = "font" 的资源优先级
Network tab of Chrome's DevTools listing a number of font resources. They are a mix of Low and High priority.
BBC 新闻详细信息页面上 type = "script" 的资源优先级。

当优先级更改时,您可以在 Big request rows 设置或工具提示中看到初始优先级和最终优先级。

Network tab of Chrome's DevTools. The 'Big request rows' setting is ticked and the Priority column shows the first image with a prioruty of High, and a different initial priority of medium below. The same is shown in the tooltip.
DevTools 中的优先级更改。

何时可能需要 Fetch Priority?

现在您了解了浏览器的优先级逻辑,您可以调整页面的下载顺序,以优化其性能和 Core Web Vitals。以下是一些您可以更改以影响资源下载优先级的示例

  • 按照您希望浏览器下载它们的顺序放置资源标记(例如 <script><link>)。具有相同优先级的资源通常按照它们被发现的顺序加载。
  • 使用 preload 资源提示来更早地下载必要的资源,特别是对于浏览器不容易早期发现的资源。
  • 使用 asyncdefer 来下载脚本,而不会阻止其他资源。
  • 延迟加载折叠下方的的内容,以便浏览器可以将可用带宽用于更关键的折叠上方的资源。

这些技术有助于控制浏览器的优先级计算,从而提高性能和 Core Web Vitals。例如,当预加载关键背景图像时,可以更早地发现它,从而改进 Largest Contentful Paint (LCP)。

有时,这些处理可能不足以针对您的应用程序最佳地确定资源的优先级。以下是一些 Fetch Priority 可能有帮助的场景

  • 您有多个折叠上方的图像,但并非所有图像都应具有相同的优先级。例如,在图像轮播中,只有第一个可见图像需要更高的优先级,而其他图像(通常最初在屏幕外)可以设置为具有较低的优先级。
  • 视口内的图像通常以 Low 优先级启动。布局完成后,Chrome 会发现它们在视口中并提高它们的优先级。这通常会显着延迟关键图像(如 hero 图像)的加载。在标记中提供 Fetch Priority 可让图像以 High 优先级启动并更早开始加载。为了尝试在某种程度上自动化此过程,Chrome 将前五个较大的图像设置为 Medium 优先级,这将有所帮助,但显式的 fetchpriority="high" 会更好。

    对于作为 CSS 背景包含的 LCP 图像的早期发现,仍然需要预加载。要提高背景图像的优先级,请在预加载中包含 fetchpriority='high'
  • 将脚本声明为 asyncdefer 会告诉浏览器异步加载它们。但是,如优先级表所示,这些脚本也被分配了“Low”优先级。您可能希望在确保异步下载的同时提高它们的优先级,特别是对于对用户体验至关重要的脚本。
  • 如果您使用 JavaScript fetch() API 异步提取资源或数据,浏览器会为其分配 High 优先级。您可能希望某些提取以较低的优先级运行,特别是如果您将后台 API 调用与响应用户输入的 API 调用混合使用。将后台 API 调用标记为 Low 优先级,将交互式 API 调用标记为 High 优先级。
  • 浏览器为 CSS 和字体分配 High 优先级,但其中一些资源可能比其他资源更重要。您可以使用 Fetch Priority 来降低非关键资源的优先级(请注意,早期 CSS 是渲染阻塞的,因此通常应该是 High 优先级)。

fetchpriority 属性

使用 fetchpriority HTML 属性来指定资源类型(例如 CSS、字体、脚本和图像)在使用 linkimgscript 标记下载时的下载优先级。它可以采用以下值

  • high:资源具有更高的优先级,并且您希望浏览器将它的优先级设置为高于通常优先级,只要浏览器的自身启发式方法不会阻止这种情况发生。
  • low:资源具有较低的优先级,并且您希望浏览器降低它的优先级,同样,如果其启发式方法允许这样做。
  • auto:默认值,允许浏览器选择合适的优先级。

以下是一些在标记中使用 fetchpriority 属性以及脚本等效 priority 属性的示例。

<!-- We don't want a high priority for this above-the-fold image -->
<img src="/images/in_viewport_but_not_important.svg" fetchpriority="low" alt="I'm an unimportant image!">

<!-- We want to initiate an early fetch for a resource, but also deprioritize it -->
<link rel="preload" href="/js/script.js" as="script" fetchpriority="low">

<script>
  fetch('https://example.com/', {priority: 'low'})
  .then(data => {
    // Trigger a low priority fetch
  });
</script>

浏览器优先级和 fetchpriority 的影响

您可以将 fetchpriority 属性应用于不同的资源,如下表所示,以提高或降低其计算优先级。每行中的 fetchpriority="auto" (◉) 标记了该资源类型的默认优先级。(也可作为 Google 文档 获取)。

  在布局阻止阶段加载 在布局阻止阶段一次加载一个
Blink
优先级
VeryHigh High Medium Low VeryLow
DevTools
优先级
最高 High Medium Low 最低
主资源
CSS(早期**) ⬆◉
CSS(后期**)
CSS(媒体不匹配***) ⬆*** ◉⬇
脚本(早期**或非来自预加载扫描器) ⬆◉
脚本(后期**)
脚本 (async/defer) ◉⬇
字体
字体 (rel=preload) ⬆◉
导入
图像(在视口中 - 布局后) ⬆◉
图像(前 5 张图像 > 10,000 像素2
图像 ◉⬇
媒体(视频/音频)
XHR(同步)- 已弃用
XHR/fetch*(异步) ⬆◉
预取
XSL

fetchpriority 设置相对优先级,这意味着它会适当提高或降低默认优先级,而不是显式地将优先级设置为 HighLow。这通常会导致 HighLow 优先级,但并非总是如此。例如,具有 fetchpriority="high" 的关键 CSS 保留“Very High”/“Highest”优先级,并且在这些元素上使用 fetchpriority="low" 保留“High”优先级。这两种情况都不涉及显式地将优先级设置为 HighLow

用例

当您想为浏览器提供关于以什么优先级提取资源的额外提示时,请使用 fetchpriority 属性。

提高 LCP 图像的优先级

您可以指定 fetchpriority="high" 以提高 LCP 或其他关键图像的优先级。

<img src="lcp-image.jpg" fetchpriority="high">

以下比较显示了使用和不使用 Fetch Priority 加载 LCP 背景图像的 Google Flights 页面。通过将优先级设置为 high,LCP 从 2.6 秒提高到 1.9 秒

使用 Cloudflare Workers 重写 Google Flights 页面以使用 Fetch Priority 进行的实验。

使用 fetchpriority="low" 来降低不立即重要的折叠上方图像的优先级,例如图像轮播中屏幕外的图像。

<ul class="carousel">
  <img src="img/carousel-1.jpg" fetchpriority="high">
  <img src="img/carousel-2.jpg" fetchpriority="low">
  <img src="img/carousel-3.jpg" fetchpriority="low">
  <img src="img/carousel-4.jpg" fetchpriority="low">
</ul>

虽然图像 2-4 将在视口之外,但它们可能被认为“足够接近”以将它们提升到 high 甚至在添加 load=lazy 属性时也加载。因此,fetchpriority="low" 是此问题的正确解决方案。

在早期与 Oodle 应用的实验中,我们使用此方法来降低加载时不显示的图像的优先级。它使页面加载时间减少了 2 秒。

A side-by-side comparison of Fetch Priority when used on the Oodle app's image carousel. On the left, the browser sets default priorities for carousel images, but downloads and paints those images around two seconds slower than the example on the right, which sets a higher priority on only the first carousel image.
仅对第一个轮播图像使用高优先级可使页面加载更快。

降低预加载资源的优先级

为防止预加载资源与其他关键资源竞争,您可以降低它们的优先级。将此技术与图像、脚本和 CSS 一起使用。

<!-- Lower priority only for non-critical preloaded scripts -->
<link rel="preload" as="script" href="critical-script.js">
<link rel="preload" as="script" href="non-critical-script.js" fetchpriority="low">

<!-- Preload CSS without blocking render, or other resources -->
<link rel="preload" as="style" href="theme.css" fetchpriority="low" onload="this.rel='stylesheet'">

重新确定脚本的优先级

您的页面需要交互的脚本应快速加载,但不应阻止其他更关键的渲染阻塞资源。您可以将这些标记为具有高优先级的 async

<script src="async_but_important.js" async fetchpriority="high"></script>

如果脚本依赖于特定的 DOM 状态,则不能将其标记为 async。但是,如果它们在页面上稍后运行,则可以以较低的优先级加载它们

<script src="blocking_but_unimportant.js" fetchpriority="low"></script>

当解析器到达此脚本时,这仍将阻止解析器,但将允许优先处理此脚本之前的内容。

如果需要完整的 DOM,则另一种选择是使用 defer 属性(在 DOMContentLoaded 之后按顺序运行),甚至在页面底部使用 async

降低非关键数据提取的优先级

浏览器以高优先级执行 fetch。如果您有多个可能同时触发的提取,则可以将高默认优先级用于更重要的数据提取,并降低不太关键的数据的优先级。

// Important validation data (high by default)
let authenticate = await fetch('/user');

// Less important content data (suggested low)
let suggestedContent = await fetch('/content/suggested', {priority: 'low'});

Fetch Priority 实现说明

Fetch Priority 可以在特定用例中提高性能,但在使用 Fetch Priority 时需要注意一些事项

  • fetchpriority 属性是一个提示,而不是指令。浏览器会尝试尊重开发人员的偏好,但它也可以应用其资源优先级偏好来解决冲突。
  • 不要将 Fetch Priority 与预加载混淆

    • Preload 是一种强制提取,而不是提示。
    • Preload 让浏览器提前发现资源,但它仍然以默认优先级提取资源。相反,Fetch Priority 无助于可发现性,但它确实允许您提高或降低提取优先级。
    • 通常,观察和衡量预加载的效果比优先级更改的效果更容易。

    Fetch Priority 可以通过提高优先级的粒度来补充预加载。如果您已经将预加载指定为 <head> 中 LCP 图像的第一个项目之一,那么 high Fetch Priority 可能不会显着提高 LCP。但是,如果预加载发生在其他资源加载之后,则 high Fetch Priority 可以更多地提高 LCP。如果关键图像是 CSS 背景图像,请使用 fetchpriority = "high" 预加载它。

  • 在更多资源争夺可用网络带宽的环境中,优先排序带来的加载时间改进更加相关。这在 HTTP/1.x 连接(其中不可能并行下载)或低带宽 HTTP/2 或 HTTP/3 连接上很常见。在这些情况下,优先级排序可以帮助解决瓶颈。

  • CDN 并未统一实现 HTTP/2 优先级,HTTP/3 也类似。即使浏览器从 Fetch Priority 传递优先级,CDN 也可能不会按指定的顺序重新确定资源的优先级。这使得测试 Fetch Priority 变得困难。优先级在浏览器内部以及支持优先级的协议(HTTP/2 和 HTTP/3)中均适用。仅为了独立于 CDN 或源支持的内部浏览器优先级,仍然值得使用 Fetch Priority,因为当浏览器请求资源时,优先级通常会发生变化。例如,在浏览器处理关键的 <head> 项时,通常会阻止请求低优先级资源(如图像)。

  • 您可能无法在初始设计中将 Fetch Priority 作为最佳实践引入。在开发周期的后期,您可以查看分配给页面上不同资源的优先级,如果它们与您的期望不符,您可以引入 Fetch Priority 以进行进一步优化。

开发者应将 preload 用于其预期用途—预加载解析器未检测到的资源(字体、导入、背景 LCP 图像)。preload 提示的位置会影响资源何时预加载。

Fetch priority 关注的是资源在被提取时应如何提取。

使用预加载的技巧

使用预加载时请记住以下几点

  • 在 HTTP 标头中包含预加载会将其置于加载顺序中所有其他内容之前。
  • 通常,对于任何具有 Medium 或更高优先级的项,预加载都按照解析器到达它们的顺序加载。如果您在 HTML 的开头包含预加载,请务必小心。
  • 字体预加载可能最适合放在 head 的末尾或 body 的开头。
  • 导入预加载(动态 import()modulepreload)应在需要导入的 script 标记之后运行,因此请确保先加载或解析 script,以便可以在加载其依赖项时对其进行评估。
  • 图像预加载默认具有 LowMedium 优先级。相对于异步脚本和其他低优先级或最低优先级标记对其进行排序。

历史记录

Fetch Priority 最早于 2018 年在 Chrome 中作为源试用进行实验,然后在 2021 年再次使用 importance 属性。当时它被称为 Priority Hints。此后,该接口已更改为 HTML 的 fetchpriority 和 JavaScript Fetch API 的 priority,这是 Web 标准流程的一部分。为了减少混淆,我们现在将此 API 称为 Fetch Priority。

结论

随着预加载行为的修复以及近期对 Core Web Vitals 和 LCP 的关注,开发者可能对 Fetch Priority 感兴趣。他们现在拥有额外的控制旋钮来实现他们首选的加载顺序。