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:assets for 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.
Request order before and after priority hints Two stacked request waterfalls. Before, the hero image starts late behind CSS and other images. After, fetchpriority high and a preload move the hero request to the front so it finishes far sooner. Hero request order: default vs priority hint Before styles.css logo + icons hero.avif (LCP) LCP 2.7s After hero.avif (preload, high) LCP 1.6s styles.css logo + icons Earlier in the waterfall means an earlier paint — the hero is no longer waiting behind low-value requests.
The default order leaves the hero queued behind CSS and decorative images; a preload plus fetchpriority high moves it to the front of the 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.

Which priority hints the hero needs A decision tree starting from the hero image. First question: is it found early by the preload scanner? If yes, the priority prop alone is enough. If no, a manual preload is added. A second question then asks if the hero is served from another origin; if so, a preconnect is added on top of the preload. Which priority hints does the hero need? Hero image on the page Found early by the preload scanner? Served from another origin? Priority prop is enough fetchpriority=high + loading=eager Add a manual rel=preload so the late hero is discovered up front rel=preload + rel=preconnect warm the cross-origin connection first Yes No No Yes
Start from the hero: found early by the scanner, the 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 reads Low or Medium, 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 carries fetchpriority="high" and loading="eager". If Astro's priority prop 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:

ChangeHero request startLCPFCP
Baseline (<Image>, no hints)910 ms2.7s1.9s
+ priority (eager + fetchpriority)240 ms2.0s1.8s
+ rel=preload (late-discovery hero)120 ms1.6s1.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

  • priority on 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 matching imagesrcset and imagesizes, or lean on priority alone and let the <img> carry the hint across whichever candidate wins.
  • preconnect without matching crossorigin: a mismatched or missing crossorigin on 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 priority prop 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.
The three hints and what each one does Three panels. fetchpriority high raises the request priority of a known LCP image. Preload makes a resource discoverable before the parser reaches it. Loading eager prevents the lazy-loading heuristic from deferring an above-the-fold image. Each names the mistake it causes when over-applied. The three hints and what each one does fetchpriority="high" raises priority of a known request use on the LCP image only free — no extra request over-use: everything is high, so nothing is rel="preload" discoverable before the parser gets there for resources referenced late costs bandwidth if wrong over-use: competes with the LCP image loading="eager" opts out of lazy loading required for above-fold images the default for the first image over-use: below-fold images load too early Apply to exactly one image per page: the one Chrome reports as the LCP element.
The three are complementary but not interchangeable — each addresses a different reason the browser was late.

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.

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.