Optimizing LCP on Astro with Priority Hints
Astro pre-renders HTML, so the hero image is usually present in the markup from the first byte. What still goes wrong is priority: the browser finds the hero alongside every other image and CSS request and gives it no special treatment, so it queues behind less important resources. The result is a hero that could paint quickly but doesn't, because the network is busy fetching a logo, three icons, and a stylesheet first. Priority hints fix this by telling the browser which single resource matters most, so it fetches the largest contentful paint element ahead of the queue.
This page is the Astro-specific recipe for the discovery-time span of LCP, within the broader Performance Optimization & Core Web Vitals for SSGs work. You will add the priority prop, decide whether a manual preload is warranted, warm up a cross-origin connection when the hero lives on an image CDN, and confirm each change with a real measurement rather than assuming it helped.
Prerequisites
- An Astro project (v3 or later) using
astro:assetsfor images. If you have not set that up, start with Image Optimization Pipelines in Astro. - A deployed URL (a preview deploy is fine) so you can measure real headers and waterfalls — local dev does not reproduce edge timing.
- Lighthouse (or
lhci) and access to a WebPageTest run for the network waterfall.
The Recipe
1. Mark the hero with the priority prop
Astro's <Image> component ships a priority shorthand. Setting it flips three things at once: loading="eager", fetchpriority="high", and no lazy loading. Use it on the hero only:
---
import { Image } from 'astro:assets';
import hero from '../assets/hero.jpg';
---
<Image
src={hero}
alt="Product dashboard overview"
widths={[800, 1200]}
sizes="(max-width: 800px) 100vw, 1200px"
format="avif"
priority
/>
The rendered <img> carries fetchpriority="high" and loading="eager", so the browser fetches it ahead of the default-priority images it discovers later. Because <Image> also emits explicit width/height, it reserves layout space and avoids the shift that would otherwise hurt CLS.
2. Add a preload only when discovery is late
The priority prop does not emit a <link rel="preload">. If your hero is a CSS background-image, or rendered by a component that the preload scanner reaches late, add the preload by hand in the document head:
---
// src/layouts/Base.astro
---
<head>
<link rel="preload" as="image" href="/_astro/hero.HASH.avif"
fetchpriority="high" />
</head>
For a normal <Image priority> that the parser already finds at the top of the body, skip the preload — it would duplicate a request the browser is already prioritizing. Reach for it only when the network panel shows the hero starting late.
3. Keep priority exclusive
fetchpriority="high" is relative. If the logo, three feature thumbnails, and the hero are all high, the browser cannot tell which one to fetch first and the hint does nothing. Leave every non-LCP image at default (or loading="lazy" if below the fold) so the hero is the only high request on the page.
priority prop alone is enough; discovered late, add a rel=preload; and if it lives on another origin, add a rel=preconnect on top.4. Preconnect when the hero is on another origin
If the hero is served from an image CDN — a separate hostname such as images.example.com or a transformation service — the high-priority fetch still has to open a fresh connection first: DNS lookup, TCP handshake, TLS negotiation. That setup can cost 100–300 ms on mobile before a single byte of the image arrives, and no amount of fetchpriority compresses it. Warm the connection up front with a preconnect so it is ready when the priority fetch fires:
---
// src/layouts/Base.astro
---
<head>
<link rel="preconnect" href="https://images.example.com" crossorigin />
<link rel="preload" as="image" fetchpriority="high"
href="https://images.example.com/hero.avif" />
</head>
The crossorigin attribute must match how the image is actually requested (anonymous images are cross-origin), or the browser opens a second, unshared connection and the preconnect is wasted. This is the same connection-setup concern covered for third-party assets in the delivery layer; here it applies specifically to the one resource on the critical path.
5. Verify the hint actually landed
A priority hint that silently fails to apply is worse than none, because you stop looking. Confirm it three ways before trusting it:
- DevTools Network panel: right-click the column header, enable Priority, and reload. The hero row must read
High. If it readsLoworMedium, the attribute never reached the rendered<img>. - Lighthouse: run it and open the Largest Contentful Paint element audit — it names the exact element the browser measured. If that element is not your hero, you are prioritising the wrong resource.
<img>inspection: view the rendered markup and check the hero carriesfetchpriority="high"andloading="eager". If Astro'spriorityprop did not flip both, the import or the component wrapper is intercepting it.
Only after all three agree should you record a before/after number.
Measured Impact
Measured on a documentation landing page deployed to a CDN, throttled mobile profile (4x CPU, ~1.6 Mbps), median of five Lighthouse runs:
| Change | Hero request start | LCP | FCP |
|---|---|---|---|
Baseline (<Image>, no hints) | 910 ms | 2.7s | 1.9s |
+ priority (eager + fetchpriority) | 240 ms | 2.0s | 1.8s |
+ rel=preload (late-discovery hero) | 120 ms | 1.6s | 1.8s |
The hint moved the hero request from 910 ms to 240 ms after navigation start, and LCP fell from 2.7s to 2.0s. Adding the preload (this page's hero was set via CSS, so the scanner found it late) shaved another 0.4s to 1.6s. WebPageTest confirmed the hero moved to the front of the waterfall in both filmstrip and request log.
Pitfalls & Rollback
priorityon multiple images: the most common mistake. It dilutes the signal; one hero only.- Preload pointing at a stale hash: Astro fingerprints asset filenames, so a hard-coded preload URL breaks on the next build. Generate the URL from the imported asset or omit the preload and rely on
priority. - Preloading an off-screen image: preloading a resource that is not the LCP element wastes bandwidth and can delay the real LCP. Confirm with the Lighthouse LCP element audit.
- Preloading one srcset candidate: when the hero uses a responsive
srcset, a preload that hard-codes a single URL can fetch a width the browser would never have chosen, so it downloads twice. Preload with matchingimagesrcsetandimagesizes, or lean onpriorityalone and let the<img>carry the hint across whichever candidate wins. preconnectwithout matchingcrossorigin: a mismatched or missingcrossoriginon the preconnect opens a connection the actual image request cannot reuse, so you pay the handshake twice. Match it to how the image is fetched.- Rollback: removing the
priorityprop and any manual preload or preconnect line reverts the behavior completely — there is no cache or build state to clear. Re-run Lighthouse to confirm you are back to baseline.
Conclusion
Priority hints are the cheapest LCP win available to an Astro site: one priority prop on the hero, a rel=preload only when the hero is discovered late, and a rel=preconnect when it lives on another origin. On the example page they cut LCP from 2.7s to 1.6s without changing a single byte of the image — then verify each step so a silently dropped attribute never costs you the win. Pair them with format and sizing work in Reducing LCP from Hero Images on Static Sites and the render-delay fixes in Eliminating Render-Blocking CSS on Static Sites for the full LCP picture.
FAQ
What does the Astro Image priority prop actually do?
The priority prop on Astro's <Image> component sets loading to eager and fetchpriority to high, and skips lazy loading, in one shorthand. It does not emit a preload link, so for a CSS background or a late-discovered image you still add a manual rel=preload in the head.
Should I use fetchpriority high on more than one image?
No. Priority is relative, so marking several images high tells the browser they are all equally important and the benefit disappears. Reserve fetchpriority="high" for the single LCP element and let everything else load at default or lazy priority.
Do I need both a preload link and fetchpriority high?
Not always. If the hero is a normal <img> the preload scanner finds early, fetchpriority="high" alone is enough. Add a preload only when the resource is discovered late, such as a CSS background image, and then measure that it actually helped rather than wasted bandwidth.
How do I confirm the priority hint worked?
Run Lighthouse and check the LCP value and the "Largest Contentful Paint element" audit, and look at the network panel or a WebPageTest waterfall to confirm the hero request now starts near the top of the waterfall instead of behind other images. In DevTools, add the Priority column to the Network panel and confirm the hero row reads High.
Does fetchpriority high work with a responsive srcset hero?
Yes. The browser first picks the srcset candidate that matches the viewport and device pixel ratio, then applies the fetchpriority="high" hint to that chosen URL. You do not mark individual candidates; the hint lives on the <img> element and follows whichever source the browser selects — which is exactly what Astro's <Image> widths and sizes emit.
Do priority hints help if the hero loads from an image CDN on another domain?
The hint still raises the request priority, but a cross-origin hero also pays a DNS, TCP and TLS cost before the first byte. Add a rel="preconnect" to that origin in the head so the connection is warm by the time the high-priority fetch fires; the two work together rather than replacing each other.
Related
- Parent: Largest Contentful Paint Optimization for Static Sites — the full LCP workflow this fits into.
- Reducing LCP from Hero Images on Static Sites — sizing and format for the hero.
- Eliminating Render-Blocking CSS on Static Sites — cut the render-delay span.
- Image Optimization Pipelines in Astro — the
astro:assetssetup behind<Image>.