Boost Resource Loading With fetchpriority, A New Priority Hint
fetchpriority
and explore some potential use cases.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 |
|
High |
|
Medium |
|
Low |
|
Lowest |
|
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.
We can run Lighthouse test to check the metrics and use this data for comparison.
Please note: Several factors can affect the stats, so the results might differ, but the general gist is the same.
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>
<!-- ... -->
Let’s run Lighthouse on the modified example, and we can notice that our LCP has improved.
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.
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.
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);
}
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
andscript
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