Speed Lab: fetchpriority and finding the main image

Two controlled tests of how the main image loads: how the browser finds it (HTML, CSS or script, with and without preload) and what fetchpriority adds.

IntermediateHow-to13 min readUpdated October 30, 2026

In this guide
  1. The question
  2. How I tested it
  3. Test 1: how the image is found
    1. Results
    2. When the request started
    3. What Lighthouse said
    4. The same test on a shaped network
  4. Test 2: priority on a crowded page
  5. Test 3: the CSS background on the crowded page
  6. Test 4: the crowded page over HTTP/2, on a shaped network
  7. What these tests do not show
  8. What Chrome does with priorities by itself
  9. What to do
  10. Mistakes to avoid
  11. Check your own page
  12. Sources

The question

web.dev's guide to Largest Contentful Paint says that two things decide how soon the main image starts to load: when the browser finds it, and what priority the browser gives it. I tested both on the same page, with nine Lighthouse runs for each variant. The first test changes how the image is found: as a normal img in the HTML, as a CSS background, or inserted by a script, with and without a preload. The second test changes the priority of the image on a page where other images compete with it.

The 2025 Web Almanac gives the setting. fetchpriority="high" was on 17% of mobile pages that had an image as their Largest Contentful Paint element, and rel="preload" on about 2%. The Almanac adds that preload is only necessary when the image is not quickly discoverable in the HTML document, and that it should then be paired with fetchpriority="high".

How I tested it

I used the method set out in how the Speed Lab tests are run, and the lazy-loading lab has the script for running it yourself. In short: Lighthouse 13.5.0 and a headless Chrome for Testing 153, the Lighthouse mobile preset with devtools throttling (a 562.5 ms delay on each request, 1,474.56 Kbps, a processor four times slower, a 412 by 823 screen), and a small Node server on the same machine sending plain HTTP/1.1 with no compression and no caching. Tests 1 to 3 use that setup. Test 4 uses a shaped network, which it describes. The hero is a generated JPEG of 1,200 by 675 pixels and 189,585 bytes, with width and height set. Each variant ran nine times, taken in turns, and the tables show the median with the lowest and highest run in brackets.

Test 1: how the image is found

The page has a header, a heading, the hero, a paragraph, a small style sheet and a 52 KB script with defer. Six variants:

<!-- a. a normal img in the HTML -->
<img class="hero" src="hero.jpg" width="1200" height="675" alt="Spring collection">

<!-- b. the same, with a high priority -->
<img class="hero" src="hero.jpg" width="1200" height="675" alt="Spring collection" fetchpriority="high">

<!-- c. a CSS background: .hero-box { background: url(hero.jpg) center/cover } -->
<div class="hero-box" role="img" aria-label="Spring collection"></div>

<!-- d. the same, with a preload in the head -->
<link rel="preload" as="image" href="hero.jpg" fetchpriority="high">
<div class="hero-box" role="img" aria-label="Spring collection"></div>

<!-- e. an empty slot that app.js fills with the img after it has run -->
<div class="hero-box" id="hero-slot"></div>

<!-- f. the same, with the preload in the head -->
<link rel="preload" as="image" href="hero.jpg" fetchpriority="high">
<div class="hero-box" id="hero-slot"></div>

Results

VariantLCP, median (lowest to highest)Compared with a
a. img in the HTML2,491 ms (2,478 to 2,501)
b. img with fetchpriority="high"2,496 ms (2,479 to 2,513)5 ms later
c. CSS background2,789 ms (2,779 to 2,806)298 ms later
d. CSS background with preload2,499 ms (2,482 to 2,515)8 ms later
e. Inserted by a script3,078 ms (3,073 to 3,089)587 ms later
f. Inserted by a script with preload2,501 ms (2,477 to 2,519)10 ms later

First Contentful Paint was between 1,212 and 1,221 ms in every variant, Total Blocking Time and Cumulative Layout Shift were 0, and the page weight was 237.6 KiB for all six. Only the moment the image arrived changed.

Three results stand out.

  1. `fetchpriority="high"` made no difference here. Variants a and b are within the noise of each other.
  2. A CSS background cost about 300 ms and a script-inserted image cost about 590 ms. Their ranges do not overlap with variant a.
  3. A preload brought both back to the baseline. Variants d and f are within 10 ms of variant a.

When the request started

The timeline in the Lighthouse report shows why. These are the median times, in milliseconds from the first request.

VariantHero requestedHero finishedStyle sheet finishedScript finished
a. img in the HTML6132,4701,1851,732
b. img with high priority6172,4741,1891,735
c. CSS background1,1892,7621,1811,461
d. CSS background with preload6112,4701,1831,739
e. Inserted by a script1,4813,0521,1861,463
f. Inserted by a script with preload6162,4751,1881,743

The normal image is requested at 613 ms, together with the style sheet and the script. The CSS background cannot be requested until the style sheet has arrived and been read: its request starts at 1,189 ms, 8 ms after the style sheet finished. The script version waits for the script to run: its request starts at 1,481 ms, 18 ms after the script finished. A preload in the head puts the request back at about 611 to 616 ms.

This matches web.dev's description. It lists the cases where the browser cannot find the image from the HTML alone: an img added by JavaScript, an address hidden in data-src and a CSS background. In each, the browser must run the script or apply the style sheet first. For those cases web.dev's advice is to preload the image with a high fetch priority.

What Lighthouse said

The "LCP request discovery" insight in Lighthouse 13.5 checks three things: whether fetchpriority="high" is applied, whether the request is discoverable in the first HTML, and whether the image is not lazy-loaded. Its score is 1 only when all three pass. Variants b, d and f scored 1 in all nine runs. Variants c and e failed the second check. Variant a passed the second and third checks and failed the first, so its score was 0, although it was as fast as variant b.

The same test on a shaped network

A chain of requests, such as the style sheet and then the image, looks long in the lab, which adds 562.5 ms to each request. So I ran the six variants a second time on the shaped network of Test 4 below, five runs each.

VariantUsual throttlingAgainst aShaped networkAgainst a
a. img in the HTML2,491 ms1,832 ms (1,825 to 1,857)
b. img with high priority2,496 ms5 ms later1,834 ms (1,825 to 1,889)2 ms later
c. CSS background2,789 ms298 ms later1,848 ms (1,833 to 1,859)16 ms later
d. CSS background with preload2,499 ms8 ms later1,836 ms (1,773 to 1,864)4 ms later
e. Inserted by a script3,078 ms587 ms later2,090 ms (2,063 to 2,129)258 ms later
f. Inserted by a script with preload2,501 ms10 ms later1,833 ms (1,805 to 1,838)1 ms later

The preload still took away all of the cost. The cost itself was smaller, and for the CSS background it fell inside the noise: 16 ms against 298. The hero is 189 KB on a link of 1.6 Mbit/s, so the page is limited by the link. The CSS background asked for the hero 174 ms later, but the link was busy with the script in the meantime, and the hero finished at about the same time. A late request costs most when the link would otherwise sit idle. The script-inserted image still lost 258 ms, because the script that adds it finishes later than the style sheet does.

Test 2: priority on a crowded page

The first page had no competition, so a priority hint had little to decide. This page has six thumbnails, each about 53 KB, in a two-column grid under the hero. All six sit inside the first screen of the test phone, so the browser treats them as important too. The pages are otherwise the same: the style sheet, the 52 KB script and the 189,585-byte hero, in 550.4 KiB and 11 requests, one of them the favicon. Four variants:

<!-- a. default: no hints -->
<img class="hero" src="hero.jpg" width="1200" height="675" alt="Spring collection">
<img src="thumb1.jpg" width="600" height="400" alt="Sample 1"> <!-- and five more -->

<!-- b. high priority on the hero only -->
<img class="hero" src="hero.jpg" width="1200" height="675" alt="Spring collection" fetchpriority="high">

<!-- c. low priority on the six thumbnails only -->
<img src="thumb1.jpg" width="600" height="400" alt="Sample 1" fetchpriority="low">

<!-- d. both -->
VariantLCP, median (lowest to highest)FCP, median
a. Default4,214 ms (4,197 to 4,223)1,220 ms
b. Hero fetchpriority="high"4,214 ms (4,196 to 4,226)1,221 ms
c. Thumbnails fetchpriority="low"4,215 ms (4,199 to 4,231)1,216 ms
d. Both4,207 ms (4,199 to 4,233)1,222 ms

The four ranges overlap completely. The hints did not change when the hero arrived.

The timeline shows that the hints did change something. In the default page, the browser rated the hero and all six thumbnails High, and requested the first three thumbnails at the same moment as the hero, at about 620 ms, and the last three at about 1,200 ms.

When I marked the thumbnails Low, the browser still requested the first four thumbnails as before, but it held back thumbnails 5 and 6 until about 2,700 ms. The hero was requested at 617 ms in both pages, and it finished at 4,190 ms in both. Over plain HTTP/1.1 with Lighthouse's throttling, the hero shares the limited connection with whatever else is downloading, and delaying the last two thumbnails did not shorten its download. I did not work out why. What I can say is that the hint changed the order of the queue and not the result.

Two points follow. Chrome already treats images that sit in the first screen as important, so a hint on one image of several equals gives little. And over HTTP/1.1 with this throttling there is nothing for a server to schedule: the limit is applied inside the browser, after the bytes have arrived. Test 4 repeats the page over HTTP/2 with a server that follows priorities. This result is "no effect in this setup", not "never an effect". web.dev's advice stands: use the hint on the one or two images most likely to be the main element, and check the effect on your own page.

Test 3: the CSS background on the crowded page

Test 1 found that a CSS background costs time on a page with nothing else to load. I repeated it on the crowded page of Test 2, with the hero as a CSS background in the first four variants and the plain img as the reference.

VariantLCP, median (lowest to highest)
a. CSS background, no hint4,578 ms (4,552 to 4,603)
b. With a preload4,230 ms (4,208 to 4,249)
c. Preload with fetchpriority="high"4,217 ms (4,204 to 4,235)
d. As c, with the six thumbnails at fetchpriority="low"4,212 ms (4,202 to 4,242)
e. Plain img in the HTML4,212 ms (4,199 to 4,225)

The background cost 366 ms more than the plain image, close to the 298 ms on the page with nothing else. The request for it started at 1,210 ms, 20 ms after the style sheet finished. With a preload it started at about 615 ms. The preload took away all but 18 ms of the cost. The high priority on the preload added nothing measurable: 13 ms, inside the noise. The thumbnails at low priority changed nothing further.

Test 4: the crowded page over HTTP/2, on a shaped network

The tests above ran over HTTP/1.1 with Lighthouse's usual throttling, which limits the download inside the browser, after the bytes have arrived. A server's choice of what to send first cannot show there. So I ran the crowded page of Test 2 again on a shaped network: a round trip of 150 ms, one download link of 1,638.4 Kbps shared by all connections, and slow start. The test of many small files has the details and the code. I used HTTP/1.1, and HTTP/2 from Hypercorn, a server that schedules its responses by the priorities the browser sends. Seven runs per variant.

VariantHTTP/1.1: LCPHTTP/2: LCPHTTP/2: hero finished
a. Default3,346 ms (3,282 to 3,360)2,175 ms (1,663 to 2,185)2,152 ms
b. Hero fetchpriority="high"3,350 ms (3,326 to 3,365)2,425 ms (1,671 to 3,457)2,400 ms
c. Thumbnails fetchpriority="low"3,342 ms (3,268 to 3,393)1,911 ms (1,675 to 1,954)1,887 ms
d. Both3,341 ms (3,296 to 3,368)1,932 ms (1,660 to 1,966)1,905 ms

Three results stand out.

  1. Over HTTP/1.1 the hints changed nothing, as in the first tests: about 3,345 ms in all four variants. The hero is the biggest file on a link shared by up to six connections, and it finishes last whatever the order of the queue.
  2. HTTP/2 brought the main image in 1,171 ms sooner with no hints at all (2,175 ms against 3,346 ms).
  3. Marking the six thumbnails as low priority helped by a further 264 ms (1,911 ms against 2,175 ms). In six of the seven runs of each variant the two do not meet: the default page finished its hero at 2,142 to 2,162 ms, and the page with low thumbnails at 1,882 to 1,908 ms. A few runs of three variants finished the hero near 1,650 ms, and I did not find out why. fetchpriority="high" on the hero did not help: its median was later (2,425 ms) and its runs were spread from 1,671 to 3,457 ms.

The request timelines show what the server did. In the default page the hero, the first thumbnail and the 52 KB script finished together at about 2,150 ms, the second thumbnail followed at 2,400 ms and the last four at 3,420 ms. With the thumbnails marked low, the hero and the script finished together at 1,887 ms, and the thumbnails came after them, from 2,140 ms to 3,410 ms. The hint changed what the server sent first, which is what it is for. Lighthouse's usual throttling could not show this: with Node's HTTP/2 server the median LCP was 4,223, 4,229, 4,224 and 4,217 ms in the four variants.

What these tests do not show

What Chrome does with priorities by itself

In a separate check of ten tiny pages in Chrome for Testing 153, I recorded the priority of the hero request. A plain img started at Medium and rose to High once layout showed it was in the viewport. fetchpriority="high" started it at High. A CSS background, a script-inserted image and a bare preload all started at Low and rose to High later. loading="lazy" started at Low and stayed there. The image attributes cheat sheet lists all ten cases. These are observations of one browser build, not rules.

What to do

  1. Find your LCP element. The Lighthouse report names it.
  2. Make it a real `img` in the HTML, with its address in src and its size in width and height.
  3. If it must be a CSS background or must be added by a script, preload it in the head with fetchpriority="high".
  4. Add `fetchpriority="high"` to the one image that is likely to be the LCP element, after checking that it helps your own page. Do not add it to many images.
  5. If many images compete in the first screen, such as a grid of thumbnails, mark the ones that matter less with fetchpriority="low". Over HTTP/2 this moved the main image 264 ms sooner in Test 4, and over HTTP/1.1 it did nothing. Measure it on your own page.
  6. Read Lighthouse's "LCP request discovery" insight as a prompt to check, and use your own measurement to decide.

See how to fix a slow LCP and resource hints for the full set of steps.

Mistakes to avoid

Check your own page

Run the free audit and find the Largest Contentful Paint element. Check whether it is an img in the HTML. If it is a background or is added by a script, add a preload, run the audit again and compare the time.

Sources

Start here

Free website audit

Live Google data for any public page: mobile and desktop.

Mobile + desktop · Lighthouse lab test · Real-user Chrome data when Google has it

Rather have this done for you? Speed optimization · Book a call

Related guides

All guides →