LCP (Largest Contentful Paint) almost always comes down to an image: on the vast majority of marketing and content sites, the LCP element is the hero image, not text. To hit the 2.5-second threshold, you need to solve four problems at once: pick the right format (AVIF or WebP instead of JPEG), serve the browser the correct size through srcset and sizes, raise the load priority with fetchpriority="high", and avoid breaking everything with a stray loading="lazy". Below are concrete compression numbers, working code, and a comparison of how Astro Image, Next.js Image, and manual markup each handle this.
In Astro and Next.js performance projects, we consistently see the same picture: PageSpeed reports an LCP of 3.5-4.5 seconds even though the developer already picked a lightweight framework, trimmed unnecessary JavaScript, and set up a CDN. The problem isn’t the stack - the hero image weighs 1-2 MB as a JPEG, ships without srcset, without fetchpriority, and sometimes even with loading="lazy" on an element the browser is supposed to load first.
Most English-language material on this topic mentions WebP as a single bullet point in a generic SEO checklist, with no comparison to AVIF, no compression numbers, and no code for srcset/sizes. This article closes exactly that gap: format, implementation, load priority, and what Astro and Next.js automate for you.
Slow LCP directly hurts conversion and rankings: Google uses Core Web Vitals as a ranking factor, and users close the tab if the main screen hasn’t rendered within 2.5-3 seconds. On landing pages and marketing sites where the hero image fills the entire viewport, this isn’t an abstract metric - it’s the first second of contact with the brand, and a single HTTP request is responsible for it.
LCP (Largest Contentful Paint): the time until the largest visible element on the screen renders - in most cases, the hero image or a block with a background image. The “good” threshold is under 2.5 seconds; “poor” is over 4 seconds.
Why LCP Is Almost Always an Image
Before optimizing format and priority, you need to know exactly which element Google counts as LCP on a given page - it isn’t always obvious.
PageSpeed Insights and Lighthouse have a “Largest Contentful Paint element” section that points directly at the DOM node that became the LCP. In Chrome DevTools, it’s the Performance tab: an LCP marker appears on the Timings track, and clicking it highlights the element on the page. Most often it’s an <img> in the hero section, less often a large text block or a CSS background-image.
A full breakdown of measurement methodology - CrUX vs. Lighthouse, threshold values across all three Core Web Vitals metrics - is covered in our article on optimizing Core Web Vitals on WordPress. This piece focuses only on LCP and only on images, because they determine the outcome on 80-90% of sites with a hero banner or product cover shot. CLS and INP aren’t covered here - INP has its own dedicated breakdown.
AVIF vs WebP vs JPEG: Compression Numbers
Format choice is the first and highest-impact lever for LCP optimization, because file weight directly determines download time.
AVIF: a format built on the AV1 codec, compressing images roughly 50% harder than JPEG and 20-30% harder than WebP at comparable visual quality, especially on photos and gradients. Browser support in 2026 exceeds 95% of global traffic; Safari gained support back in version 16.4, in March 2023.
WebP: compresses 25-35% harder than JPEG and is supported almost universally - around 97% of browsers. It encodes 5-10x faster than AVIF, which matters for sites with real-time user image uploads.
JPEG: only needed now as a fallback for legacy browsers and email clients - unjustifiable as a primary format on the 2026 web.
| Format | Compression vs. JPEG | Browser support | Encoding speed | When to use |
|---|---|---|---|---|
| JPEG | baseline | ~100% | fast | fallback |
| WebP | -25…-35% | ~97% | fast | safe default for all images |
| AVIF | -50% (photos and gradients - more) | ~95%+ | 5-10x slower than WebP | hero image, LCP element, high-traffic pages |
Practical takeaway: WebP is the safe default for the entire image gallery on a site. AVIF is worth applying deliberately to the hero image - the one element that determines LCP, where a 20-30% weight reduction converts directly into seconds of load time. Encoding an entire site’s photo library into AVIF in real time is a bad idea because of encoding speed, but for static generation at build time (Astro, Next.js with static export) it’s a non-issue: encoding happens once at deploy, not on every user request.
The browser picks the best supported format on its own through a <picture> tag with multiple sources:
Example: picture with AVIF, WebP, and JPEG fallback
<picture>
<source srcset="/hero.avif" type="image/avif">
<source srcset="/hero.webp" type="image/webp">
<img src="/hero.jpg" alt="Description" width="1200" height="630" fetchpriority="high">
</picture>
The browser checks the sources top to bottom and loads the first supported format. The fetchpriority attribute goes on the final <img>, not on <source>.
Responsive Images in Practice: srcset and sizes
The second most common mistake after format is serving the same file to every device, regardless of screen width.
If a hero image weighs 400 KB at 3000px and is served to a phone with a 375px-wide screen, the browser downloads the full file and then squeezes it down in CSS - a pure waste of bandwidth and time-to-LCP. The srcset and sizes attributes solve this natively in HTML, no JavaScript required.
srcset lists the available file variants with their width in the w descriptor. sizes tells the browser what width the image will occupy on screen under different viewport conditions - the browser uses this information to pick the right file from srcset before it even starts parsing CSS.
Example: img with srcset and sizes for a hero image
<img
src="/hero-1200.webp"
srcset="/hero-480.webp 480w, /hero-800.webp 800w, /hero-1200.webp 1200w, /hero-1920.webp 1920w"
sizes="(max-width: 768px) 100vw, (max-width: 1200px) 80vw, 1200px"
alt="Description"
width="1200"
height="630"
fetchpriority="high"
>
The optimal number of variants in srcset is 3-5. More variants increase the load on CDN caching and the build process; fewer reduces the accuracy of size matching per device.
A common mistake is specifying srcset but forgetting sizes. Without sizes, the browser defaults to assuming the image will occupy 100% of the viewport width, and in most cases picks a file larger than actually needed - the entire benefit of responsive images disappears.
fetchpriority=“high” and preload Without Changing Format
Even a perfectly compressed AVIF image can still cause poor LCP if the browser finds out about it too late - and that’s where load priority comes in.
The browser runs a preload scanner - a mechanism that scans HTML that hasn’t fully loaded yet, looking for resources to download without waiting for the full DOM to be built. The browser loads ordinary images at normal priority, alongside dozens of other resources on the page. The fetchpriority="high" attribute explicitly tells the browser: this resource matters more than the others, download it immediately.
In Google’s own tests (Addy Osmani, Chrome DevRel), adding a single fetchpriority="high" attribute to the LCP image improved LCP from 2.6 to 1.9 seconds - with no change to format, size, or hosting. The rule is simple: the attribute goes on exactly one image per page - the one that is actually the LCP element. Mark five images as high priority, and the browser has no way of knowing which one really matters, and the benefit disappears.
For critical cases (for example, an image the browser only discovers after loading CSS with a background-image), you can explicitly preload the resource with <link rel="preload"> in <head>:
Example: preload for the LCP image in head
<link rel="preload" as="image" href="/hero-1200.webp" fetchpriority="high">
If the image is already served through srcset, modern browsers support the imagesrcset and imagesizes attributes directly on the preload tag, so the preload matches the same set of variants.
loading=“lazy” Breaks LCP - A Common Mistake
The loading="lazy" attribute was designed for images below the fold - and it’s the “just in case” habit of applying it to every image on the page that most often wrecks LCP.
When the browser encounters loading="lazy", the preload scanner deliberately skips that resource on its first pass through the HTML - loading is deferred until the element approaches the viewport during scroll. For a hero image the user sees within the first second, this creates a delay of 300 milliseconds to 1.5 seconds, depending on how late the browser rediscovers the element.
The rule has no exceptions: the LCP image gets loading="eager" (or no attribute at all, which is the default) paired with fetchpriority="high". loading="lazy" stays strictly for below-the-fold images - galleries, product cards, footer content.
A frequent source of this mistake is CMSs and site builders that automatically apply loading="lazy" to every <img> on the page through a global theme or plugin setting, without distinguishing the hero block from the rest of the content. This is the first thing to check if LCP dropped after a theme change or after adding a new image-optimization plugin.
Astro Image, Next.js Image, or Manual Markup
All the code above can be written by hand, but modern frameworks automate most of this work - the question is how much control you need.
Astro <Image /> and <Picture />. The component from astro:assets converts images to WebP by default at build time, with no runtime API call. For multiple formats at once, use <Picture formats={['avif', 'webp']} /> - Astro generates static files for all formats at deploy, with no server load on every request. This is what sets Astro apart from tools with runtime conversion: optimization happens once at build, not on every user visit.
Next.js <Image />. The next/image component proxies images through a built-in on-the-fly optimization API by default, unless the site builds to a fully static export. For the LCP image, you need to explicitly pass the priority prop (priority in current versions, preload starting with Next.js 16) - it’s what inserts <link rel="preload"> into <head> automatically. The sizes prop is mandatory for a fill layout - without it, the image downloads at the full viewport width regardless of the container’s actual size.
Manual markup. Justified when the site isn’t built on Astro or Next.js - for example, a custom static site generator, a legacy CMS without a format-conversion plugin, or a situation that requires full control over CDN image transforms (Cloudflare Images, imgix, Cloudinary). In this case, all the <picture>, srcset, and fetchpriority code from the previous sections is written by hand or generated at build time by a custom script.
For projects moving off an old stack that never had this kind of automation, the architecture breakdown and migration techniques are covered in our article on migrating a site to a modern stack - once you move to Astro or Next.js, image optimization stops being a manual task for every new picture.
Real Case: Images as the Sole Cause of Poor LCP
In a typical performance project for a Next.js marketing site, LCP sat at 4.1 seconds despite a fully green audit on every other front - minified JS, a configured CDN, no blocking scripts.
The cause turned out to be entirely the hero image: a 1.4 MB JPEG at 2400x1260px, no srcset, no fetchpriority, with loading="lazy" inherited from a global card-component setting.
What changed:
- Converted the hero image to AVIF with a WebP fallback via
<picture>- the primary variant’s weight dropped from 1.4 MB to 240 KB - Added
srcsetwith four width variants (480w, 800w, 1200w, 2400w) and a correctsizesvalue - Removed the inherited
loading="lazy", replaced it withfetchpriority="high" - Changed nothing about hosting, CDN, or the rest of the page’s code
Result after a single deploy cycle, based on CrUX data:
- LCP: 4.1s -> 1.8s (moved from the “poor” zone into the “good” zone)
- Hero image weight: 1.4 MB -> 240 KB (5.8x smaller)
- Other metrics (CLS, INP) didn’t change - confirming the problem was isolated to the image itself
Key takeaway: nearly the entire LCP gain came from working on a single file, with no infrastructure or framework changes involved.
Who This Matters For
This optimization is especially critical for companies whose site has one clear LCP element - a marketing landing page with a hero banner, a product page with a large photo, a blog with article cover images. If PageSpeed reports an LCP above 2.5 seconds on an otherwise healthy stack (no heavy page builder, no excessive third-party scripts), the cause is the image in 8 cases out of 10, not the architecture.
If an audit shows the problem is systemic - not just one image but the site’s entire image pipeline lacks the right formats and sizes - it’s worth looking at ongoing site performance support: CrUX monitoring, regular audits of new images entering the site through the CMS.
Frequently Asked Questions
How do I know if an image is actually causing poor LCP?
Open the PageSpeed Insights or Lighthouse report and find the “Largest Contentful Paint element” section - it points directly at the DOM element. If it’s an <img> or a block with background-image, optimizing images will solve most of the problem. If the LCP element is a text block or a video, working on images won’t help - look at fonts and blocking scripts instead.
Do I need to convert every image on the site to AVIF, or just the hero?
No. AVIF delivers its biggest impact specifically on the LCP element, where file weight directly affects a ranking metric. For the rest of the gallery (cards, thumbnails, in-article images), WebP is the more practical choice: comparable bandwidth savings while encoding 5-10x faster, which matters when processing hundreds of images in bulk through a CMS.
Why doesn’t fetchpriority="high" work if the image is already in WebP?
Format and load priority solve different problems. fetchpriority doesn’t reduce file weight - it only makes the browser fetch that specific resource earlier than others. If the file itself weighs 1.5 MB even as WebP, priority speeds up when the download starts, not how long the download itself takes. Format and priority work together as two separate tools, not substitutes for each other.
What do I do if the CMS automatically applies loading=“lazy” to every image?
Most modern CMSs and builders let you override this behavior for a specific image - through a component parameter, a custom field, or a manual edit to the hero-block template. If the theme doesn’t expose that option, loading="lazy" can be forcibly removed via JavaScript after render, but that’s less reliable than correct server-side markup - a component that explicitly supports fetchpriority and eager loading is the better choice.
How long does it take to optimize LCP through images on a real site?
If the problem is isolated to a single hero image (the common case), the fix takes a few hours: converting to AVIF/WebP, setting up srcset and sizes, adding fetchpriority. Changes show up in CrUX after about 28 days, since the data updates on a rolling window. If the problem is systemic - the entire image pipeline in the CMS doesn’t generate the right formats and sizes - build-level or CDN-level automation is needed, which is a 1-2 week task.
If PageSpeed reports an LCP above 2.5 seconds and the rest of the stack looks healthy, describe the problem to the Exceltic.dev team. We’ll check exactly where the time is being lost on a specific hero image and propose a plan - from a targeted format and srcset fix to full automation through Astro or Next.js Image components.