Put fetchpriority="high" on the <img> that is your Largest Contentful Paint element, and on nothing else. Chrome starts images at Low priority and only raises the one in the viewport after layout, so the hint lets the hero image request go out at High priority as soon as the parser finds it, instead of waiting for layout. Leave loading="lazy" off that image.
The attribute does nothing for an image the browser cannot see yet, a preload for a responsive hero has to repeat its srcset, and Next.js and Astro now set it for you under different prop names. Those cases are below.
What does fetchpriority actually change?
It is a hint about one resource relative to others of the same type. The attribute takes high, low or auto, with auto as the default, and it works on <img>, <link> and <script>. The fetch() equivalent is the priority option in RequestInit. Per web.dev’s Fetch Priority guide, it shipped in Chrome and Edge 102, Safari 17.2 and Firefox 132, and MDN lists it as Baseline 2024, newly available since October 2024.
The reason it matters for LCP is the default. web.dev describes images as starting at Low priority; Chrome only discovers they are in the viewport once layout is complete, and then boosts them. Since Chrome 117 the first five large images (over 10,000 square pixels) get Medium instead, which helps, but Medium still ranks below the stylesheets and <head> scripts Chrome fetches at higher priority. With fetchpriority="high" the image is High from the moment the preload scanner sees the tag, so there is no wait for layout.
web.dev cites Google Flights going from 2.6s to 1.9s LCP after boosting the priority of its hero image. That is one site with one hero. The attribute does not shrink the file; it changes when the request starts and what it competes with, so the gain is largest where the image was sitting in a queue.
The browser treats it as a hint and keeps its own heuristics, so it can ignore you. Put it on six images and they compete with each other again, because “high relative to other images” means little when they are all high.
Is the LCP image in the HTML at all?
fetchpriority only helps if the browser can find the request early. web.dev’s LCP optimisation guide splits LCP into four subparts: time to first byte, resource load delay, resource load duration and element render delay. It recommends keeping resource load delay under 10% of the total. The attribute attacks that delay, and it cannot do anything for an image that only exists after JavaScript runs.
These patterns hide the image from the preload scanner:
- an
<img>inserted by client-side rendering after hydration - a lazy-loading library that keeps the real URL in
data-srcuntil a script swaps it in - a CSS
background-imageon the hero section, which the browser only finds after downloading and parsing the stylesheet
The first two are best fixed by server-rendering a plain <img src>. For the background image, preload it and set the priority on the preload:
<link rel="stylesheet" href="/styles.css">
<link rel="preload" as="image" href="/img/hero.webp" type="image/webp" fetchpriority="high">
A preload without fetchpriority gets discovered early but still downloads at the default image priority, so add both.
Why does a lazy-loaded hero image fail LCP?
loading="lazy" tells the browser to wait until it knows where the image sits relative to the viewport, which means waiting for layout. web.dev puts it bluntly: never lazy-load your LCP image. Look for a component library that defaults every image to lazy, or a CMS template that renders every image through one partial; either can put the attribute on the hero without anyone choosing it.
Chrome’s LCP request discovery insight, in Lighthouse and the DevTools Performance panel, checks for exactly this trio: is fetchpriority=high applied, is the request discoverable in the initial document, and is lazy loading absent. If you preload the image, the insight wants fetchpriority=high on the preload as well as on the <img>.
How do I preload a responsive hero image?
If your hero uses srcset and sizes, a plain preload with href names one file, and on screens where srcset picks a different candidate the browser downloads both. MDN’s <link> reference documents imagesrcset and imagesizes for this, valid only with rel="preload" and as="image", mirroring the image’s own attributes:
<link
rel="preload"
as="image"
imagesrcset="/img/hero-800.webp 800w, /img/hero-1600.webp 1600w"
imagesizes="100vw"
fetchpriority="high"
>
<img
src="/img/hero-1600.webp"
srcset="/img/hero-800.webp 800w, /img/hero-1600.webp 1600w"
sizes="100vw"
width="1600"
height="900"
alt="Workshop bench with a half-built keyboard"
fetchpriority="high"
>
Keep the values identical to the image’s own srcset and sizes, so the preload and the <img> choose the same file.
You often do not need the preload at all. If the <img> is already in the server-rendered HTML near the top of <body>, the preload scanner finds it quickly, and fetchpriority="high" on the tag is enough. Preload earns its place when the image sits late in a long document or is referenced from CSS.
With <picture>, the attribute goes on the inner <img>. The <source> elements only supply candidate URLs; the <img> is the element that makes the request.
What about carousels and pages with several heroes?
Carousels are where people overuse the attribute. Only the first slide is visible at load, so it gets fetchpriority="high" and the rest get fetchpriority="low". That keeps the hidden slides out of the way of the one that counts without lazy-loading them, which risks a blank slide when someone clicks next. web.dev notes a related quirk: a low image near the viewport will still load even with loading="lazy", because it is close enough to count.
Layouts that swap heroes by breakpoint are harder. If the mobile and desktop LCP elements are different images, marking both high makes them compete. Use one <picture> with media-specific sources, so there is one <img> and one hint.
How do Next.js and Astro set it?
Both frameworks wrap these attributes in a prop.
In Astro, the <Image /> and <Picture /> components gained a priority prop in Astro 5.10. Setting it adds loading="eager", decoding="sync" and fetchpriority="high" in one go, and you can still override each attribute.
In Next.js 16, the next/image docs deprecate priority in favour of a new preload prop, which inserts a <link> in the <head>. The same page then says that in most cases you should use loading="eager" or fetchPriority="high" instead of preload, and not to use preload together with either of those. So on a Next.js 16 hero, the default choice is:
import Image from 'next/image';
export function Hero() {
return (
<Image
src="/img/hero.webp"
width={1600}
height={900}
sizes="100vw"
alt="Workshop bench with a half-built keyboard"
fetchPriority="high"
/>
);
}
Reach for preload only when the image is discovered late. If you are upgrading from 15, search for priority on <Image> and decide per image rather than renaming it.
How do I check it worked?
First, confirm which element is the LCP. Paste this into the console on a fresh load; the last entry it logs is the final candidate:
new PerformanceObserver((list) => {
const entries = list.getEntries();
const last = entries[entries.length - 1];
console.log('LCP', Math.round(last.startTime), 'ms', last.element, last.url);
}).observe({ type: 'largest-contentful-paint', buffered: true });
MDN’s LargestContentfulPaint page documents element and url on each entry. If url is empty, your LCP element is text, and the image advice above does not apply.
Then open the Network panel, right-click a column header and enable Priority. Turn on “Big request rows” and the column shows the initial and final priority. An LCP image that reads Low then High is being boosted after layout, which means the hint is missing. It should read High from the start, and the request should appear near the top of the waterfall rather than after the scripts have finished.
Check on a throttled mobile profile. On a fast desktop connection everything arrives quickly and the ordering barely shows.
What we would do
On any page with an image hero: server-render the <img>, remove loading="lazy" from it, add fetchpriority="high", and stop there unless the Network panel shows the request starting late. Add a preload only for CSS backgrounds or images the scanner reaches late, and give the preload the same priority. Mark hidden carousel slides low. Do not spray high across the page, and do not expect it to rescue an image that a client component renders after hydration; fix the rendering first. If the LCP element turns out to be text, look at fonts and render-blocking CSS instead, and if the page loads quickly but feels sluggish, the problem is interaction rather than loading, which our INP diagnosis guide covers. For speeding up the next page load rather than this one, see our Speculation Rules write-up.
Whoooop does this kind of work as part of website speed optimisation: finding which subpart of LCP is costing you, then fixing the template that causes it rather than the one page that got measured.