DEV Community

Cover image for Priority Hints and optimizing LCP
Kevin Farrugia
Kevin Farrugia

Posted on • Originally published at imkev.dev

Priority Hints and optimizing LCP

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" />
Enter fullscreen mode Exit fullscreen mode

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' })
Enter fullscreen mode Exit fullscreen mode

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.

WebPageTest waterfall chart illustrating the initial phase

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.

WebPageTest waterfall chart illustrating DOM Interactive

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.

WebPageTest waterfall chart illustrating crossorigin images

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.

WebPageTest waterfall chart illustrating  raw `preconnect` endraw  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.

WebPageTest waterfall chart illustrating  raw `preload` endraw  directive

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"
/>
Enter fullscreen mode Exit fullscreen mode

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=""
/>
Enter fullscreen mode Exit fullscreen mode

This should increase the initial priority of the image from Low to High, allowing the image to be picked up in the initial phase.

WebPageTest waterfall chart illustrating  raw `fetchpriority="high"` endraw

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.

Screenshot from Firefox Web Developer Tools illustrating the initial phase

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.

Screenshot from Firefox Web Developer Tools illustrating the initial phase

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.

Screenshot showing Safari Web Inspector tight mode

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.

Screenshot showing Safari Web Inspector not restricting crossorigin  raw `Low` endraw  priority requests

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.

Screenshot of Safari Web Inspector illustrating the  raw `preload` endraw  directive

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"
/>
Enter fullscreen mode Exit fullscreen mode

Browsers that support Priority Hints will preload the resource using the assigned fetchpriority, while browsers that do not will use the preload directive.

WebPageTest waterfall chart showing  raw `preload` endraw  and  raw `fetchpriority` endraw

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.

WebPageTest waterfall chart illustrating the opportunity for the LCP image with a horizontal red line

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.

Combination chart showing *opportunity* vs LCP

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.

Bar chart showing distribution of opportunity by initial priority

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.

Combination chart showing the number of render-blocking resources vs median *opportunity*

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.

Distribution of opportunity

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, use fetchpriority to hint the browser to download your image earlier.
  • You can use preload to prioritize your LCP image on Firefox until fetchpriority is supported. Safari does not download images earlier when using the preload directive.

Let me know what you think. Your feedback is welcome. ♥

Related links

Special thanks

Special thanks to Barry Pollard for his advice and valuable feedback.


Photo by Denise Jans on Unsplash

Top comments (0)