Priority Hints and optimizing LCP
Kevin Farrugia
Posted on January 2, 2023
Cross-posted from https://imkev.dev/fetchpriority-opportunity
Priority Hints are used to indicate to the browser the relative priority of a resource. You can set Priority Hints by adding the fetchpriority
attribute to <img>
, <link>
, <script>
, and <iframe>
elements or through the priority
attribute on the Fetch
API.
The browser's loading process is complex. Browsers determine a request's priority mostly by its type and its position in the document's markup. For example, a CSS file requested in the document's <head>
will be assigned the Highest
priority, while a <script>
element with the defer
attribute will be assigned the Low
priority. The browser downloads resources with the same priority in the order in which they are discovered.
fetchpriority
The fetchpriority
attribute can be used to hint the browser to increase or decrease the priority of a requested resource. The enumerated attribute can have one of three values:
-
high
- The resource is more important relative to its default priority -
low
- The resource is less important relative to its default priority -
auto
- The default value
<img src="/lcp.jpg" alt="A dog" fetchpriority="high" />
In the example above, we are hinting to the browser that the <img>
priority is more important than its default priority.
The same values are supported for the priority
attribute on the fetch
method.
fetch("/api/data.json", { priority: 'high' })
In the fetch
request above, we are indicating to the browser that the fetch
request has an increased priority compared to its default priority.
Default priority
Priority Hints increase or decrease a resource's priority relative to its default priority. For example, images - by default - always start at a Low
priority. Assigning fetchpriority="high"
will increase their priority to High
. On the other hand, a render-blocking stylesheet is assigned a Highest
priority by default. Assigning it fetchpriority="low"
will lower its priority to High
- but not Low
. fetchpriority
is used to adjust a resource's priority relative to its default, rather than to explicitly set its value.
The influence of Priority Hints on resource prioritization in Chromium documents the different resource types, their default priority (◉), and the resultant priority when using fetchpriority="high"
(⬆) and fetchpriority="low"
(⬇).
Note that if an image is discovered to be within the viewport, then its priority is boosted to High
. However, this could be quite late in the loading process and may have little or no impact if the request was already sent. Using fetchpriority="high"
allows you to tell the browser to start in High
priority, rather than waiting for the browser to find out if it is in the viewport or not.
"Tight mode"
Most browsers download resources in two phases. During the initial phase (Chromium also refers to this as "Tight mode"), the browser does not download Low
priority resources unless there are less than two in-flight requests.
In the waterfall chart above, you could see that the resource image-1.jpg
does not start downloading until style-2.css
has finished downloading - even if it was discovered immediately. At this point, only one resource remains in-flight - script.js
, so the browser begins to download the Low
priority image.
The initial phase is completed once all blocking scripts in the <head>
have been downloaded and executed (scripts with async
or defer
are not render-blocking). Even if there are more than two in-flight requests, the browser can now proceed to download any remaining resources based on their priority and the order in which they appear in the markup.
In the chart above, once the render-blocking JavaScript is downloaded and executed (pink bar), the browser begins downloading the images, even if the two CSS files are still in-flight. The yellow vertical bar illustrates DOM Interactive - or when the readystatechange
event was fired.
preconnect
If the images reside on a separate domain, the browser needs to open a connection to the domain before downloading the files.
This is shown on the WebPageTest chart with the green, orange, and magenta bars preceding the downloading. We can start downloading the images earlier using the preconnect
resource hint.
In the chart above, the connection to the cdn.glitch.global
domain is opened during the initial phase - before the browser is able to start downloading the files. Once the browser exits the initial phase (yellow vertical line) it begins downloading the images immediately - saving approximately 350ms.
preload
If we were able to improve the download time using the preconnect
resource hint, are we able to improve it further using the preload
directive? Short answer: no. The preload
directive allows you to inform the browser about critical resources that are "late-discovered". This is especially useful for resources loaded inside stylesheets or scripts, such as background-images or fonts. In our example, the image is declared in the markup and discovered early, so preload has little effect.
In the chart above, we have replaced the preconnect
hint with the following:
<link
rel="preload"
as="image"
href="https://cdn.glitch.global/.../image-1.jpg"
/>
Despite the preload, the image still doesn’t begin downloading until there are less than two requests in-flight.
fetchpriority
We can use Priority Hints to indicate to the browser that image-1.jpg
is more important than its default priority using:
<img
src="https://cdn.glitch.global/.../image-1.jpg"
fetchpriority="high"
alt=""
/>
This should increase the initial priority of the image from Low
to High
, allowing the image to be picked up in the initial phase.
The waterfall chart above shows that image-1.jpg
is picked up during the initial phase, in parallel with the other critical resources. This gives us the greatest improvement so far.
Firefox
Firefox uses similar heuristics to determine which resources should be loaded during the initial phase. However, differently from Chromium-based browsers, it does not begin downloading any Low
priority resources until all JavaScript in the <head>
is downloaded and executed - even when there is only one High
priority request in-flight.
The above screenshot is taken from Firefox Web Developer Tools and shows that the image resources (rows 5 - 8) are fetched after the script (row 2) is downloaded and executed and the page becomes interactive
- vertical, blue line.
While Chrome waits for JavaScript declared in the <head>
to be downloaded and executed, Firefox waits for all render-blocking JavaScript declared before the image elements - even if these are declared outside of the <head>
.
Firefox does not support fetchpriority
yet, however, we can increase the priority of image-1.jpg
using the preload
directive.
In the screenshot above, the file image-1.jpg
is fetched in parallel with the other resources. This is similar to the behavior we have seen when adding fetchpriority="high"
on Google Chrome.
Safari
Safari on iOS and macOS also has an initial phase although it behaves differently than Chrome and Firefox.
Low
priority resources start being fetched when there are fewer than two in-flight requests. It is not dependent on the readystatechange
event and even on pages without any render-blocking JavaScript, the browser will wait until there is one in-flight request.
In the screenshot above, taken from Safari's Web Inspector, the images do not start downloading until style-1.css
finishes downloading and there are less than two in-flight requests.
On Safari, the initial phase only applies to resources from the same origin. If the Low
priority resources are loaded from a different domain they will be fetched as soon as they are discovered.
In the screenshot above, the crossorigin images are fetched immediately without waiting for the High
priority resources to finish downloading.
The preload
directive does not affect the resource's priority. However, placing the <link rel="preload">
directive before High
priority requests will cause it to download earlier; since at the time it is discovered there are less than two requests in-flight. This is the same behavior seen on other browsers and in most cases, I would advise against placing preload
directives above High
priority resources as render-blocking CSS should take precedence.
In this screenshot, the Low
priority file image-1.jpg
begins downloading before the High
priority style-1.css
file because the <link rel="preload">
is placed above it in the document markup.
Combining preload
with fetchpriority
Priority Hints are only supported on Chromium-based browsers so far, however, they fail gracefully on unsupported browsers that do not recognize the fetchpriority
attribute. This allows us to combine the preload
directive with Priority Hints.
<link
rel="preload"
as="image"
fetchpriority="high"
href="https://cdn.glitch.global/.../image-1.jpg"
/>
Browsers that support Priority Hints will preload the resource using the assigned fetchpriority
, while browsers that do not will use the preload
directive.
The above chart shows similar results to the one earlier which included the fetchpriority
attribute on the <img>
element. The advantage of this method is unifying an approach that prioritizes the resource on browsers that support Priority Hints and on those that do not.
fetchpriority
all the things
In this section, we will look at the potential benefit of using fetchpriority
. All data is taken from the HTTP Archive and we are only considering pages that use HTTP/2 or HTTP/3 and where the Largest Contentful Paint (LCP) element is an image. All queries and results are publicly available.
Note: The HTTP Archive data is collected using a private instance of WebPageTest using Chrome. You can learn more about their methodology.
I am assuming the benefit from fetchpriority
as the difference between the time the resource is discovered and the time it starts downloading. I refer to this as the opportunity. Therefore if a resource is discovered early but the browser starts downloading it late, then the opportunity is greater.
Note that if the images are served from a different domain, I am including the time to open the connection in the opportunity.
The chart above plots the opportunity (in milliseconds) against the LCP. The opportunity is bucketed in groups of 100ms, while anything greater than 1,000ms is grouped into a single bucket. The chart shows a strong correlation between the opportunity and the LCP - the greater the opportunity, the worse the LCP.
The above chart shows the distribution of the opportunity for mobile devices for Low
and High
priority. At the median, an LCP image requested with High
priority starts to be downloaded 21ms after it is discovered, while an LCP image with Low
priority is downloaded after 102ms. The difference grows even further at the 75th and 90th percentile.
In addition to fetchpriority="High"
, an image may have an initial High
priority if the image is late-discovered, for example when using CSS background-image
or adding an image using JavaScript. In these cases, fetchpriority
would not help since the request already has a High
priority.
We can conclude that there is a clear benefit in prioritizing your LCP image. The opportunity varies depending on your page's composition. We have already covered that Low
priority resources are not fetched immediately when there is at least one render-blocking script and two or more in-flight requests.
The above chart plots the number of render-blocking resources against the opportunity (in milliseconds). Intuitively, the more render-blocking resources your page has, the greater the delay in downloading the LCP image.
Conclusion
There is a big opportunity available to prioritize your LCP image through Resource Hints and Priority Hints. Many pages have the LCP element queued and waiting, even when it is immediately discoverable in the main document.
The above chart shows that on the median mobile website, the LCP image is queued for 98ms until the browser starts downloading it. At the 90th percentile, the LCP image is queued for 810ms. Using Priority Hints could increase the priority of the LCP image and reduce this waiting time.
There are also case studies showing an improvement to Largest Contentful Paint (LCP) after adding fetchpriority="high"
to the LCP image. Etsy saw a 4% improvement, some others reportedly saw 20-30% improvements.
Increasing the priority of a resource usually comes at the cost of another resource, so Priority Hints should be used sparingly. However, if the browser is queuing your LCP image, I recommend you experiment with Priority Hints to see if you can reduce this waiting time and improve your LCP.
In a nutshell,
- Host your LCP image on the same domain as your HTML document. If this is not possible, use
preconnect
to open an early connection. - The LCP image should be part of the document markup. If you are unable to do this, use
preload
to tell the browser to download the image before it is requested. - Avoid blocking resources when possible. If your LCP image is downloaded with a
Low
priority, usefetchpriority
to hint the browser to download your image earlier. - You can use
preload
to prioritize your LCP image on Firefox untilfetchpriority
is supported. Safari does not download images earlier when using thepreload
directive.
Let me know what you think. Your feedback is welcome. ♥
Related links
- Demos
- Queries & Results
- Optimizing resource loading with Priority Hints (web.dev)
- Prioritizing Important Page Resources With Priority Hints (debugbear.com)
Special thanks
Special thanks to Barry Pollard for his advice and valuable feedback.
Photo by Denise Jans on Unsplash
Posted on January 2, 2023
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.