fetchpriority=high Not Working on Your LCP Image
You added fetchpriority="high" to the hero image, yet DevTools still shows it starting more than a second into the load — or sitting at Low priority — and LCP has not moved.
Root cause: the hint is either never seen, never applied, or applied too late
fetchpriority is a passive attribute with no error channel. When it works, the request shifts bands; when it does not, nothing warns you — no console message, no DevTools flag, no Lighthouse complaint about a malformed hint. The failure is always one of three silences, and diagnosing which one you have determines the fix.
The browser never sees the attribute. The hint travels as literal markup, and anything that mangles the markup kills it. The HTML attribute must be exactly fetchpriority (all lowercase) with a value of high — fetchPriority="high" happens to work in HTML because attribute names are case-insensitive there, but the same camelCase string set as a DOM expando (img.fetchpriority = 'high') is inert, since the reflected property is fetchPriority. Templating layers introduce a second variant of this failure: an attribute placed on a wrapping <picture> or <a> element instead of the <img> itself does nothing, because the hint is only defined on the element that owns the fetch. The third variant is infrastructure: HTML-optimizing CDN features and “performance rewriter” proxies re-serialize markup and have a documented history of dropping attributes they do not recognise, so the attribute you see in your repository may never reach the browser.
The request the hint would apply to does not exist at parse time. The attribute participates in scheduling only when the preload scanner or parser creates the image request from markup. If the hero is painted via a CSS background-image, assembled by a client-side framework after hydration, or swapped in from a data-src by a lazy-loading library, there is no early request to promote — the fetch is created whenever script finally runs, and by then the priority band it lands in barely matters because 800–1,500 ms of discovery delay is already sunk. A perfectly-formed hint on a late-discovered image is the most common “it’s not working” report, and it is technically working — it is just irrelevant.
Another attribute overrides the intent. loading="lazy" on the same element wins outright: the browser withholds the request until the element approaches the viewport, so the high hint applies to a fetch that starts after layout, after scroll evaluation, after everything you were trying to beat. Boilerplates and CMS image plugins that stamp loading="lazy" onto every image are the usual source. A related override: a <link rel="preload"> for the same URL without the attribute fires first at the preload default (Low for images) and the element-level hint never gets a chance to schedule anything.
Minimal reproduction
All five failure modes, condensed. Each broken line looks plausible in review:
<!-- BROKEN 1: camelCase DOM expando — the reflected property is
fetchPriority; this line creates an inert property and no hint -->
<script>
const hero = document.querySelector('#hero');
hero.fetchpriority = 'high'; // silently does nothing
</script>
<!-- BROKEN 2: hint on the wrapper — picture does not own the fetch -->
<picture fetchpriority="high">
<img id="hero" src="/img/campaign.avif" width="1600" height="900" alt="Campaign hero">
</picture>
<!-- BROKEN 3: lazy wins — the request is withheld until viewport
proximity, so the priority band is moot -->
<img src="/img/campaign.avif" fetchpriority="high" loading="lazy"
width="1600" height="900" alt="Campaign hero">
<!-- FIXED: attribute on the img, eager, plus a matching preload because
this template renders the hero after hydration — preload repairs
discovery, fetchpriority repairs the band -->
<link rel="preload" as="image" href="/img/campaign.avif"
type="image/avif" fetchpriority="high">
<img src="/img/campaign.avif" fetchpriority="high" loading="eager"
width="1600" height="900" alt="Campaign hero" decoding="async">
Why a correct hint can still change nothing
The timeline below contrasts three loads of the same 210 KB hero. Only the first gives the hint anything to do early; in the other two, the request is created so late that its band is a footnote.
Deterministic fix protocol
Work top to bottom; each step either clears a cause or confirms it.
- [ ] 1. Verify the attribute in the delivered HTML. Use view-source (not the Elements panel, which shows the post-JavaScript DOM) on the production URL. Search for
fetchpriority. If it is missing but present in your repository, an intermediary — CDN HTML optimizer, edge minifier, consent-platform rewriter — is stripping it; disable that feature for the hero markup or allowlist the attribute. - [ ] 2. Confirm it sits on the
<img>element itself. Not on<picture>, not on a wrapping anchor, not on a<div>carrying the background style. If the hero is a CSS background, there is no element to hint — replace it with an<img>under the styled container, or add a preload as in step 5. - [ ] 3. Remove
loading="lazy"and any lazy-load library class from the LCP element. Check fordata-src/data-srcsetswap patterns too: ifsrcpoints at a 1 px placeholder in the raw HTML, the real request is script-created and late by construction. The LCP image must ship its real URL insrc. - [ ] 4. Check the Priority column against the start time. In the Network panel (Priority column on, Big request rows enabled), the image should read High from its first appearance — an initial Low that flips to High later means the hint is absent and you are watching the layout-time viewport boost instead. Then read the waterfall timing bars: a start beyond ~300 ms on a warm connection means discovery, not priority, is your remaining problem.
- [ ] 5. Add a matching preload for late-discovered heroes.
<link rel="preload" as="image" fetchpriority="high">in the static<head>, withimagesrcset/imagesizesmirroring the element exactly if it is responsive. Re-check the waterfall for a single request row — two rows for the hero URL means the preload and element URLs diverge. - [ ] 6. Fix any script-side casing. Grep the codebase for
\.fetchpriority\s*=— assignments through the lowercase property name are inert. UsesetAttribute('fetchpriority', 'high')or thefetchPriorityproperty, set beforesrc. - [ ] 7. Re-measure. Hard-reload under Fast 4G throttling three times; record the image’s start time, priority, and the LCP marker in the Performance panel. Then confirm at the 75th percentile in field data before closing the ticket.
Before/after metrics
Measured on a product-detail template (210 KB AVIF hero, Fast 4G emulation, cold cache, median of 9 runs). “Before” is the broken state from the reproduction — hint present in source, stripped by the CDN optimizer, plus loading="lazy" from the CMS plugin.
| Metric | Broken hint | After protocol | Delta |
|---|---|---|---|
| Hero request start | 1,640 ms | 130 ms | −1,510 ms |
| Priority at request creation | Low | High | promoted at discovery |
| Hero fetch duration | 610 ms | 560 ms | −50 ms |
| LCP (lab, p50) | 2.9 s | 1.4 s | −52% |
| LCP (field, p75, mobile) | 3.4 s | 2.1 s | −38% |
| Requests for hero URL | 2 (placeholder swap) | 1 | −1 |
Note where the improvement comes from: almost the entire gain is the request start moving earlier. The fetch duration barely changes — which is the whole lesson of this failure class. The attribute’s job is to win scheduling contention, and it can only do that for a request that exists early enough to contend.
FAQ
The Priority column shows High but LCP did not improve — why?
Band position and start time are independent. If the request starts at 1,400 ms because the browser discovered the image late, a High label cannot recover the lost time. Check startTime in the Resource Timing API: a late start means the fix is a preload or moving the image into server-rendered markup, not a priority hint.
My framework sets priority as a prop — is that enough?
Only if the rendered HTML actually contains fetchpriority="high" on the <img> element in the server response. Some component libraries map the prop to a preload link, some to the attribute, and some drop it silently on older versions. Always verify the raw HTML delivered over the network, not the JSX or template source.
Could an A/B testing or consent script be undoing the attribute?
Yes. Client-side experimentation tools that re-render the hero region clone or rebuild DOM nodes, and a rebuild that omits the attribute reverts the image to heuristic priority. Diff the element in DevTools against view-source, and if they disagree, audit every script that mutates the hero container.
Related
- The fetchpriority Attribute & Priority Hints — parent guide: shift semantics per resource type, support matrix, and rollout steps
- Deprioritizing Below-the-Fold Images with fetchpriority=low — the complementary demotion technique that frees bandwidth for the LCP fetch