Priority Hints and optimizing LCP
Tags: performance, webdev, html, webperf
Published: 2023-01-02 22:35:25

---
canonical_url: https://imkev.dev/fetchpriority-opportunity
---

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`](https://fetch.spec.whatwg.org/#fetch-method) 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`](https://wicg.github.io/priority-hints/#definitions) 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

```html
<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`](https://fetch.spec.whatwg.org/#dom-requestinit-priority) attribute on the `fetch` method.

```js
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](https://docs.google.com/document/d/1bCDuq9H1ih9iNjgzyAL0gpwNFiEP4TZS-YLRp_RuMlc/edit) 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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/wijp9tffp9q17n20j08k.png)

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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/z5t6nsc1wsz4lvdjoq6a.png)

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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/alzh3fbh0br2c4hgfit3.png)

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`](https://developer.mozilla.org/en-US/docs/Web/HTML/Link_types/preconnect) resource hint.

![WebPageTest waterfall chart illustrating `preconnect` resource hint](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/n5s2fzdebrl14m4j4dcz.png)

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`](https://developer.mozilla.org/en-US/docs/Web/HTML/Link_types/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 `preload` directive](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hcr22h7jjzl5bq9tkyss.png)

In the chart above, we have replaced the `preconnect` hint with the following:

```html
<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:

```html
<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.

![WebPageTest waterfall chart illustrating `fetchpriority="high"`](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/45i2oikpcfexbsv1y2ov.png)

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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9q49ezpyc9lg9l0x8jwq.png)

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`](https://github.com/whatwg/html/issues/7150#issuecomment-1299923593) 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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4hwd4tl8j647z1728pna.png)

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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/wc27npsz8co4ifmxt8d3.png)

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 `Low` priority requests](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/gmc4wrk3uy1flguwva85.png)

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 `preload` directive](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hcpbjlqu91x0rnfn6xny.png)

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](https://caniuse.com/?search=fetchpriority) 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.

```html
<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.

![WebPageTest waterfall chart showing `preload` and `fetchpriority`](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vei6eue4ldom42hthswz.png)

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](https://httparchive.org) and we are only considering pages that use HTTP/2 or HTTP/3 and where the [Largest Contentful Paint](https://web.dev/lcp) (LCP) element is an image. All [queries](https://github.com/kevinfarrugia/fetchpriority-opportunity) and [results](https://docs.google.com/spreadsheets/d/11QwVrr1zcFGBhOMNe25uFBD9VUfg4JP3AUkhWjod9RY/edit?usp=sharing) 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](https://httparchive.org/faq#how-is-the-data-gathered)._

![WebPageTest waterfall chart illustrating the opportunity for the LCP image with a horizontal red line](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/e9wz1rbzxw20v8t8utl7.png)

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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/boeq6o0srcq28pytcny6.png)

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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/eask0hs4tpdtxfog3nk1.png)

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*](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/sp8klfsesbjn1d8cnfot.png)

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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ruq7yckdhoe5rsbrdf42.png)

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](https://web.dev/lcp) (LCP) after adding `fetchpriority="high"` to the LCP image. [Etsy saw a 4% improvement](https://www.etsy.com/codeascraft/priority-hints-what-your-browser-doesnt-know-yet), 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

- [Demos](https://fetchpriority-opportunity.glitch.me/)
- [Queries](https://github.com/kevinfarrugia/fetchpriority-opportunity) & [Results](https://docs.google.com/spreadsheets/d/11QwVrr1zcFGBhOMNe25uFBD9VUfg4JP3AUkhWjod9RY/edit?usp=sharing)
- [Optimizing resource loading with Priority Hints](https://web.dev/priority-hints/) (web.dev)
- [Prioritizing Important Page Resources With Priority Hints](https://www.debugbear.com/blog/priority-hints) (debugbear.com)

## Special thanks

Special thanks to [Barry Pollard](https://webperf.social/@tunetheweb) for his advice and valuable feedback.

---

Photo by [Denise Jans](https://unsplash.com/@dmjdenise?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText)</a> on [Unsplash](https://unsplash.com/s/photos/cycle-lane?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText)