Reducing LCP from Hero Images on Static Sites

On most marketing and documentation landing pages the hero image is the Largest Contentful Paint (LCP) element — it is the biggest thing in the first viewport, so the browser counts its paint time as your LCP. If that image is a 1.4 MB JPEG loaded at default priority, your LCP is whatever time it takes that file to arrive and render. This guide is the hero-image recipe: right size, right format, preloaded, and never lazy-loaded. It is the image-focused companion to Largest Contentful Paint Optimization for Static Sites, inside the larger Performance Optimization & Core Web Vitals for SSGs effort.

Prerequisites

  • A static site (Astro, Hugo, Eleventy, or Jekyll) with a hero image in the first viewport.
  • A build-time image step that can emit multiple widths and formats — see Image Optimization Pipelines in Astro for the Astro path or the Hugo equivalent.
  • Lighthouse or WebPageTest and a deployed URL to measure against.
Hero image optimization flow A large source image passes through four checkpoints: generate responsive widths, encode to AVIF and WebP, set explicit dimensions, and preload with eager loading, producing a small fast hero that paints quickly. From 1.4 MB source to a fast hero paint hero.jpg 1.4 MB Resize 800 / 1200 / 2400 Encode AVIF + WebP Dimensions width/height eager + preload Paint LCP 1.6s Each checkpoint cuts bytes or moves the request earlier; the lazy-load trap is the one to avoid in between.
Four checkpoints turn a heavy source into a fast hero: resize to real widths, encode to AVIF/WebP, set explicit dimensions, then load eagerly and preload.

The Recipe

1. Generate the widths the layout actually uses

A hero rendered at 1200px CSS pixels needs an 800px width for phones and a 2400px variant for 2x screens — not a single 2400px file served to everyone. Generate the real widths and let the browser pick with srcset/sizes:

<picture>
  <source type="image/avif"
          srcset="/hero-800.avif 800w, /hero-1200.avif 1200w, /hero-2400.avif 2400w"
          sizes="(max-width: 800px) 100vw, 1200px" />
  <source type="image/webp"
          srcset="/hero-800.webp 800w, /hero-1200.webp 1200w, /hero-2400.webp 2400w"
          sizes="(max-width: 800px) 100vw, 1200px" />
  <img src="/hero-1200.jpg" alt="Analytics dashboard"
       width="1200" height="600"
       fetchpriority="high" loading="eager" decoding="async" />
</picture>

The sizes attribute tells the browser the rendered width before layout, so it downloads the right candidate on the first try instead of guessing.

2. Encode to a modern format

AVIF is typically 20-30% smaller than WebP at matched quality, and WebP is 25-50% smaller than JPEG. Emit both with the original as a final fallback. quality=80 is the sweet spot for photographic heroes; below 60 you start to see banding on gradients.

3. Set explicit dimensions

Always include width and height (or aspect-ratio). This reserves layout space so the hero does not push content down when it arrives, keeping Cumulative Layout Shift near zero. Astro's <Image> requires dimensions for exactly this reason.

4. Load it eagerly and preload it

Never put loading="lazy" on the hero — that defers the LCP element on purpose. Use loading="eager" and fetchpriority="high". If the hero is a CSS background or discovered late, add a preload; the Astro shorthand for all of this is the priority prop, covered in Optimizing LCP on Astro with Priority Hints.

Measured Impact

Measured on a marketing landing page, throttled mobile profile (4x CPU, ~1.6 Mbps), median of five Lighthouse runs:

Hero variantDelivered bytes (phone)LCPCLS
1.4 MB JPEG, lazy-loaded1.4 MB3.4s0.08
1.4 MB JPEG, eager + preload1.4 MB2.6s0.00
Responsive AVIF, eager + preload96 KB (800w)1.6s0.00

Removing loading="lazy" and adding the preload alone cut LCP from 3.4s to 2.6s. Switching to a responsive AVIF dropped the phone download from 1.4 MB to 96 KB and brought LCP to 1.6s. Setting explicit dimensions took CLS from 0.08 to 0.00. WebPageTest confirmed the hero now paints in the first frame after first byte.

Pitfalls & Rollback

  • Lazy-loading the hero: the single most common LCP regression. Keep lazy loading for below-the-fold images only.
  • One giant width for all devices: a 2400px file on a phone wastes bandwidth and slows LCP where it hurts most. Always provide smaller candidates and a sizes attribute.
  • Missing dimensions: no width/height means the browser cannot reserve space, so CLS spikes when the hero loads.
  • Quality too low to save bytes: dropping below quality=60 produces visible banding. Reach for a smaller width instead of crushing quality.
  • Rollback: revert the <picture> block to the original <img> and redeploy. Because the optimized variants are build artifacts, no cache state needs clearing — the next build regenerates or removes them.

Conclusion

The hero is usually your LCP element, so it deserves the most attention: generate real widths, encode to AVIF/WebP, set explicit dimensions, and load it eagerly with a preload. On the example page these steps moved LCP from 3.4s to 1.6s and CLS from 0.08 to zero. Combine this with the priority hints in Optimizing LCP on Astro with Priority Hints and the render-delay work in Eliminating Render-Blocking CSS on Static Sites.

FAQ

Why is the hero image so often the LCP element?

The hero is usually the largest visible element in the first viewport, which is exactly what Largest Contentful Paint measures. If it is big and downloads slowly, your LCP is its paint time, so shrinking and prioritizing it has the most direct effect on the metric.

Should I ever lazy-load a hero image?

No. loading="lazy" tells the browser to defer the image until layout determines it is near the viewport, which delays the LCP element on purpose. Use loading="eager" for anything above the fold and reserve lazy loading for images below it.

What size should I generate the hero at?

Generate the widths your layout actually renders, plus a 2x variant for high-density screens, and let srcset and sizes pick. Shipping a 2400px source into a 1200px slot doubles the download for no visible gain. Match the largest width to the largest rendered size.

Does a responsive srcset help LCP or just bandwidth?

Both. By serving a phone a smaller file than a desktop, srcset cuts the download span of LCP on mobile, where networks are slowest and LCP problems are worst. It also avoids wasting bandwidth on pixels the device cannot show.

Static Site Generators in Production