Boost Resource Loading With fetchpriority, a New Priority Hint

Boost Resource Loading With fetchpriority, a New Priority Hint

JavaScript, CSS, images, iframes, and other resources impact how quickly website loads, renders and becomes usable to the user. Loading experience is crucial to the user’s first impression and overall usability, so Google defined Largest Contentful Paint (LCP) metric to measure how quickly the main content loads and is displayed to the user.

The main content for LCP is usually the largest element located above the fold. This element could be an image, video, or simply, just a large block of text. Of all those options, it’s safe to assume that text is the best choice for LCP performance because it loads and renders fast.

Browsers follow a critical render path to parse the HTML document and its referenced resources (CSS, JS, images, etc.) to display the content on the screen. Browsers construct a render tree using DOM and CSSOM, and the page renders once all render-blocking resources like CSS, font files, and scripts have been parsed and processed.

Resource Priorities Defaults

Let’s focus on how these resources are requested and downloaded. The HTML document is the first resource to be requested and downloaded, but how do browsers determine what to download next and in which order? Browsers have a set of predetermined priorities for each resource type, so they can be downloaded in an optimal order.

Here is a rough summary according to the “Resource Fetch Prioritization and Scheduling in Chromium” by Patrick Meenan:

Priority
Highest
  • Main resource (usually a HTML document),
  • CSS (early – if requested before any non-preloaded image file) and font files;
High
  • Script (early – if requested before any non-preloaded image file),
  • Preload,
  • Image (in viewport);
Medium
  • CSS and Script (late – if requested after a non-preloaded image file).
Low
  • Script (async),
  • Media and images,
  • SVG document.
Lowest
  • CSS mismatch,
  • Prefetch.

These default priorities work reasonably well for most cases, usually resulting in a good performance. However, developers with a deep understanding of the project may want to improve performance beyond that by doing some fine-tuning under the hood. It’s common knowledge that better website performance results in more conversions, more traffic, and better user experience.

We can use the preload attribute for the HTML link element to optimize loading performance by ensuring that the browser discovers the resource earlier, downloads it, and caches it. However, that doesn’t provide us with more granular control over a particular resource type.

For example, let’s say that we are loading two render-blocking stylesheets. How can we signal to the browser that main.css file is more important than third-party-plugin.css file without resorting to using JavaScript or some other workaround?

fetchpriority HTML attribute

Enter fetchpriority HTML attribute. This new attribute can be assigned to virtually any HTML element that loads some kind of resources like images and scripts and affects its relative priority. Relative priority means that we can only affect a priority within the same resource type. Meaning that we cannot tell browsers to load images before loading the render-blocking JavaScript.

It’s important to keep in mind that this attribute doesn’t ensure that a higher-priority resource will be loaded before other (lower-priority) resources of the same type. So, fetchpriority shouldn’t be used to control the loading order itself, like in a case where we’d want a JavaScript dependency to be loaded before a script that uses it.

Also, this attribute doesn’t force the browser to fetch a resource or prevent it from fetching. It’s up to the browser if it’s going to fetch the resource or not. This attribute just helps the browser prioritize it when it is fetched.

That being said, fetchpriority attribute accepts one of the following three values:

  • low
    Decrease the relative priority of the resource.
  • high
    Increase the relative priority of the resource.
  • auto
    Default value which lets the browser decide the priority.

Going back to our previous example, we can use this attribute to signal to the browser that we want to initiate a request and download of main.css at a higher priority than the third-party-plugin.css which is the same render-blocking CSS resource as main.css.

<link rel="stylesheet" href="/path/to/main.css" fetchpriority="high" />
<link rel="stylesheet" href="/path/to/third-party-plugin.css" fetchpriority="low" />

Pretty simple, right?

Note: At the moment of writing of this article, the fetchpriority attribute is currently supported in Chrome Canary with full release set for Chrome version 101, with other browsers to follow suit.

Use It Sparingly

It’s not recommended to assign fetchpriority to every resource. Browsers already do a good enough job, so it should be used sparingly for very specific use cases where we want to prioritize requests for improving LCP, prioritize one deferred resource over the other of the same type, prioritize preload requests, etc. Over-using this attribute or running premature optimization might harm performance, so make sure to run performance tests to verify.

With that in mind, let’s move on to some of those real-world examples and scenarios, so we can use this new attribute effectively.

Examples And Use Cases

Improving Largest Contentful Paint performance

This is currently the best use-case for fetchpriority. Images are processed after all render-blocking and critical resources have already been rendered, and even using preload or loading="eager" won’t change that. However, with fetchpriority we can try to make sure the LCP image is more likely to be ready for that initial render, resulting in a considerable performance boost.

With that in mind, text block is the most optional LCP candidate in most cases, as it performs better than image or other media content. For cases where images are critical or the main part of the content, we have no option other than just to display them. So, we need to optimize them to load as quickly as possible.

Let’s take a look at a simple example of an image carousel which is also the main content in the viewport and a prime candidate for LCP.

Let’s use fetchpriority and assign a high priority to the main (active) image and low priority to thumbnails.

<!-- Carousel is above the fold -->    
<nav>
      <ul class="hero__list">
        <li>
          <img fetchpriority="low" src="..." />
        </li>
        <li>
          <img fetchpriority="low" src="..." />
        </li>

     <!-- ... -->

    <figure class="hero__figure">
      <img fetchpriority="high" src="..."></img>

    <!-- ... -->

By using fetchpriority, we marked which of the images were more important for content and which are not. So, the browser took these signals into account when fetching resources, prioritizing the main content image, which in turn allowed for the main content to show earlier, improving the LCP metric!

Deferred Images

Similarly, we can use fetchpriority attribute to prioritize below-the-fold resources that have loading="lazy" attribute. Even though this won’t affect LCP times, we can still signal the browser to prioritize the largest (active) carousel image over the small thumbnails when the browser decides to load them. That way, we can improve even the lazy loading user experience.

Remember, this attribute doesn’t force browsers to fetch a resource. Even with fetchpriority set to high, the browser will still decide if the resource is going to be fetched or not. We only signal to the browser which one of these requests is more important from each group.

<!-- Carousel is below the fold -->   

<nav>
      <ul class="hero__list">
        <li>
          <img loading="lazy"fetchpriority="low" src="..." />
        </li>
        <li>
          <img loading="lazy" fetchpriority="low" src="..." />
        </li>

     <!-- ... -->

    <figure class="hero__figure">
      <img loading="lazy" fetchpriority="high" src="..."></img>

    <!-- ... -->

Deferred Stylesheets

We can also use fetchpriority to signal which scripts and stylesheets should have a higher priority when loading.

Please note: The scripts and stylesheets remain render-blocking if they are not deferred.

Let’s take a look at the following example. If you want to follow along with this example on CodePen, make sure to inspect the configuration of the HTML tab on the CodePen example below. The code referenced below is included there, as the CodePen HTML tab only covers HTML body, and head is added with this separate config.

See the Pen Prioritizing stylesheets by Adrian Bece.

We are loading the following resources:

  • Google Fonts Stylesheet
    Defer loading right after the first render. This font switch is visible to the user (FOUT).
  • Non-Critical Below-The-Fold Stylesheet (Bootstrap is used just as an example for a more sizeable CSS file)
    Defer loading after first render with low priority, because those styles are used below-the-fold.
  • Critical CSS
    Render-blocking and applied immediately.

We’ll use a common technique to defer the loading of non-critical stylesheets and add a preload with appropriate fetchpriority to ensure that font is loaded as soon as possible, so the FOUT (Flash of unstyled text) occurs right after the first render.

<!-- Increase priority for fonts to load fonts right after the first render -->
<link rel="preload"
      as="style"
      fetchpriority="high" 
      onload="this.onload=null;this.rel='stylesheet'"
      href="https://fonts.googleapis.com/css2?family=Crete+Round&family=Roboto:wght@400;700&display=swap" />

<!-- Preload non-critical, below-the-fold CSS with low priority -->
<link rel="preload"
      as="style"
      fetchpriority="low"
      onload="this.onload=null;this.rel='stylesheet'"
      href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" />

<!-- No JS fallback for stylesheets -->
<noscript>
  <!-- -->
</noscript>

<!-- Inline critical CSS (above-the-fold styles) -->
<style>
  /* Critical CSS */
</style>

Although this configuration won’t affect LCP or some other performance metrics, it showcases how we can use fetchpriority to improve the loading experience by prioritizing one resource over the other within the same type.

See the Pen Prioritizing stylesheets - with fetchpriority by Adrian Bece.

Fine-tuning JavaScript Resource Priorities

Although we can use async and defer to change when scripts are loaded and parsed, with fetchpriority we can have more granular control over JavaScript resources.

These two examples from web.dev perfectly showcase how we can combine these attributes for even more script loading options:

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

Prioritizing JavaScript fetch Requests

This attribute is not limited to HTML, it can be also used in JavaScript fetch to prioritize some API calls over others.

For example, let’s say we are loading a blog post. We want to prioritize the main content over comments, so we need to pass a priority attribute in fetch options object.

Please note: The priority value is high by default, so we only need to assign low when we want to reduce the priority of the fetch request.

/* High-priority fetch for post content (default) */
function loadPost() {
  fetch("https://jsonplaceholder.typicode.com/posts/1")
    .then(parseResponse)
    .then(parsePostData)
    .catch(handleError);
}

/* Lower-priority fetch for comments (with priority option) */
function loadComments() {
  fetch("https://jsonplaceholder.typicode.com/posts/1/comments", {
    priority: "low"
  })
    .then(parseResponse)
    .then(parseCommentsData)
    .catch(handleError);
}

See the Pen Fetch with priority by Adrian Bece.

Embedded iframe Elements

We can assign fetchpriority to iframe elements just like any other resource. However, keep in mind that this attribute only affects the main resource of the iframe and doesn’t apply to the resources within the iframe. The browser will load resources within the iframe with default priorities. However, we are still delaying them to start later.

<iframe fetchpriority="low" type="text/html" width="640" height="390" src="http://www.youtube.com/embed/..." frameborder="0"></iframe>
Conclusion

Lately, we’ve seen exciting new features that allow us control over browser and loading behavior — CSS Cascade Layers allow us control over the CSS rule layers, and now, fetchpriority will allow us more granular control over resource loading priority. However, this control over the core concepts requires developers to be careful and use them according to best practices, as incorrect use may harm both the performance and user experience.

With that in mind, fetchpriority should be used only in specific cases, such as:

  • Improving LCP performance for image and other media resources;
  • Changing priorities of link and script resources;
  • Lowering the priority of JavaScript fetch requests which are not critical for content or functionality;
  • Lowering the priority of iframe elements.

At the time of writing of this article, this attribute is available in Chrome Canary and is set to be released in Chrome version 101, with other browsers to follow suit. It will be great to see the development community come up with more interesting use cases and performance improvements.

References

  • Priority Hints, Draft Community Group Report
  • “Optimizing Resource Loading With Priority Hints”, Leena Sohoni, Addy Osmani and Patrick Meenan