Image optimisation for page speed: the hero image and everything below it

Start with the hero image: it is often your Largest Contentful Paint. Give it priority, never lazy-load it, and size it for the first mobile viewport. Then make every other image cheap to ship and cheap to render.

By , founder of Porteur · Updated 14 September 2026 · Markdown

Fix the hero image first

Your hero image often becomes the LCP element. It sets the first impression and the Core Web Vitals pass or fail. Treat it as a first-class request.

  • Never use loading=lazy on the hero image.
  • Add fetchpriority=high on the hero img.
  • If it is a CSS background, preload it with a link rel=preload as=image and give it a high priority.
  • Serve it in AVIF or WebP with a safe JPEG fallback when needed.
  • Generate a responsive srcset and a sizes that matches your layout.
  • Set explicit width and height so the box is reserved and does not shift.
  • Place the img element in the HTML source, not inserted later with JavaScript.
  • Host it close to users on a CDN and cache it.
<!-- Example: hero with responsive sources -->
<picture>
  <source type="image/avif" srcset="/hero-640.avif 640w, /hero-960.avif 960w, /hero-1280.avif 1280w">
  <source type="image/webp" srcset="/hero-640.webp 640w, /hero-960.webp 960w, /hero-1280.webp 1280w">
  <img
    src="/hero-960.jpg"
    srcset="/hero-640.jpg 640w, /hero-960.jpg 960w, /hero-1280.jpg 1280w"
    sizes="(max-width: 640px) 100vw, (max-width: 1024px) 90vw, 1200px"
    width="1200" height="800"
    alt="Screenshot of yourproduct.com dashboard"
    fetchpriority="high"
    decoding="async"
  >
</picture>

A fixed page: the hero appears sharp on the first paint on a mid-range phone, no jump, and the rest of the page starts loading after it.

Choose the right format: AVIF, WebP or JPEG

Pick the smallest format that keeps the quality you need. Test on your own assets, not on a stock sample. Different images compress differently.

Use whenProsConsNotes
AVIFPhotographic images, gradients, UI shotsSmallest files at a given quality, supports HDR-ish cases in some browsersSlower to encode, older browsers may not supportServe with <picture> and fall back to WebP or JPEG.
WebPBroad default for photos and UIGreat compression, near-universal support as of 2026Slightly larger than AVIF on many photosGood fallback under AVIF.
JPEGComplex photos at large sizesFast to encode, widely supportedBigger files, artefacts on flat colours and textStill wins on some detailed photos after tuning.
PNGLogos, icons with sharp edges, transparencyCrisp edges, losslessLarge on photosPrefer SVG for simple shapes and logos.
SVGLogos, icons, simple illustrationsTiny, scales perfectlyNot for photosInline small SVG to avoid extra requests.
  • For a product screenshot on /pricing, AVIF often gives the best size without blur.
  • For a lifestyle photo on /about, compare AVIF, WebP and a high-quality JPEG. Keep the smallest that looks right.
  • Use PNG only when you need alpha transparency and sharp edges. Otherwise avoid it for photos.
  • SVG is best for logos and simple icons. Do not convert photos to SVG.
# Examples with commonly used encoders
# AVIF (libaom or libavif via canvas or Squoosh elsewhere)
# cwebp for WebP, mozjpeg for tuned JPEG

# WebP
cwebp -q 75 input.jpg -o output.webp

# MozJPEG
mozjpeg -quality 77 -optimized -progressive input.jpg > output.jpg

# AVIF (avifenc)
avifenc --min 20 --max 28 --cq-level 28 --jobs 4 input.png output.avif

A fixed page: /guides/getting-started serves an AVIF hero with WebP and JPEG fallbacks, and the file size halves against the old JPEG-only version.

Responsive images that match your layout

Send only the pixels the device needs. That means a proper srcset and a realistic sizes. Do not trust a default that does not match your CSS grid.

<!-- Card images in a 3-column grid above a wide breakpoint, 2-column at medium, single column on small screens -->
<img
  src="/cards/feature-640.jpg"
  srcset="/cards/feature-320.jpg 320w, /cards/feature-480.jpg 480w, /cards/feature-640.jpg 640w, /cards/feature-960.jpg 960w"
  sizes="(max-width: 640px) 100vw, (max-width: 1024px) 50vw, 33vw"
  width="640" height="400"
  alt="Feature card"
  loading="lazy"
  decoding="async"
>
  • Use width and height on every img. The browser can calculate the aspect ratio and reserve space, which prevents layout shift.
  • Keep the largest intrinsic width near the largest display size you actually use. Do not ship 4000 px images to a 1200 px layout.
  • Use decoding=async so decoding does not block rendering.
  • Prefer object-fit: cover with real image crops instead of oversized images centred in a box.

A fixed page: the cards in a grid load the 320 px files on mobile and never trigger a Cumulative Layout Shift when the images appear.

Lazy-load and defer everything below the fold

Load images late when they are not needed for the first paint. Browsers handle native lazy loading well and start fetching before the image scrolls into view.

  • Add loading=lazy on non-critical img and iframe elements.
  • Use an IntersectionObserver only when you need a custom threshold or effect. Avoid scroll event handlers.
  • Avoid huge placeholders. A simple background colour or a tiny blurred placeholder is enough.
  • Do not lazy-load icons or logos in the header. Inline critical SVG instead.
  • If a product gallery is below the fold on /product/widget, lazy-load its images and prefetch on user intent, for example on a hover or touchstart.

Google can index lazy-loaded content as long as it appears without a user action. IntersectionObserver is fine. A scroll-triggered fetch tied to an event is not.

A fixed page: the LCP hero is eager, the rest of the images have loading=lazy, and the network waterfall shows the first screen paints fast while other images queue.

Stop layout shift: reserve space and control aspect ratios

Layout shift wrecks perceived speed and your scores. It is often images without dimensions. Always give the browser enough information to lay out the page before images arrive.

  • Set width and height on every img. Modern browsers compute aspect-ratio from them.
  • For CSS background images, set a container with a fixed height or an aspect-ratio.
  • When an image slot can change height, reserve a min-height so later content does not jump.
  • Avoid text wrapping changes by defining consistent image sizes across breakpoints.

As of 2026, a good Cumulative Layout Shift is up to 0.1. Images, videos and iframes without dimensions are a common cause. Width and height fix most of these cases.

A fixed page: /blog/how-we-built-x sets width and height on the lead image and gallery, so the text below stays put while images stream in.

Background images and overlays without hurting LCP

Avoid CSS backgrounds for the hero. The browser discovers them later. If you must use one for an effect, help the browser find it early and size it correctly.

  1. Preload the file

    <link rel="preload" as="image" href="/hero-1280.avif" imagesrcset="/hero-960.avif 960w, /hero-1280.avif 1280w" imagesizes="100vw">

  2. Give the box dimensions

    Use aspect-ratio or a fixed height so the layout is stable before the image arrives.

  3. Avoid heavy overlays in the image

    Prefer a CSS gradient layer or a semi-transparent overlay element rather than baking weight into the asset.

  4. Consider switching to an <img>

    Use object-fit: cover on an img. You get srcset, sizes, and fetchpriority=high.

A fixed page: the hero remains a real img with srcset and a gradient overlay in CSS, so LCP is discoverable and fetched at high priority.

Delivery: CDNs, caching and connection hints

Fast bytes beat clever compression when your origin is slow or far. Ship images from a CDN and let the edge cache do its job. Keep connections warm for early assets.

  • Serve images over HTTP/2 or HTTP/3. A CDN handles this for you.
  • Set long cache lifetimes on versioned image URLs. Change the filename on updates.
  • Use preconnect to image origins that must load during the first paint.
  • Compress over the wire with Brotli for SVG. JPEG, WebP and AVIF are already compressed.
  • Avoid redirect chains on image URLs. Link to the final URL.
<!-- Connection hints in the head for an image CDN domain -->
<link rel="preconnect" href="https://img.yourcdn.com" crossorigin>
<!-- If the hero image is on a different host, preconnect helps the handshake start sooner -->

An image CDN that transforms on the fly is useful when you have many sizes. It should output AVIF or WebP, set width and height, and cache at the edge. Test the output quality and cache headers before you switch.

What a framework image component does for you

Modern frameworks ship image components. They solve hard defaults so you do not repeat them on every page. Use them when they match your stack.

  • Generate srcset and sizes from a single source file.
  • Convert to AVIF or WebP with fallbacks when supported by your build pipeline.
  • Add width and height to prevent layout shift.
  • Lazy-load below the fold by default and expose a priority flag for the LCP image.
  • Offer a blur-up or colour placeholder that weighs almost nothing.
  • Route image requests through a CDN or an optimiser service.

Check what it emits. View source on / and /pricing. Confirm the hero has no loading=lazy, has fetchpriority=high, and that width and height are present. If not, adjust the props or override with your own markup.

Measure the effect where it matters

Use PageSpeed Insights to see what real users get and what a single lab run says. Fix images, ship, then check again after a few days.

  • The top field section reads the Chrome User Experience Report and shows a pass or fail on the Core Web Vitals assessment.
  • The lower lab section is a Lighthouse run on a simulated mid-range phone on throttled 4G. Scores vary between runs.
  • The LCP readout in field data is the one that matters for Search. The lab LCP helps you debug changes now.
  • Total Blocking Time in lab is a proxy for interaction issues. It can delay when your hero script runs and, in turn, when images get discovered.

Largest Contentful Paint is considered good up to 2.5 seconds. Interaction to Next Paint replaced First Input Delay in March 2024. If your field data has too little traffic per URL, PageSpeed Insights falls back to origin-level data or shows none. Give your changes a few days to show in the 28-day window.

A fixed page: after moving the hero to AVIF with fetchpriority=high and adding width and height everywhere, the field LCP on Mobile improves on / from red to green over the next week.

A checklist per image

  • Is this image visible at first paint? If yes, no lazy-loading and add fetchpriority=high.
  • Is the format right? Try AVIF first, then WebP, then tuned JPEG for complex photos.
  • Does the markup include an accurate srcset and sizes that match CSS?
  • Are width and height set so no layout shift occurs?
  • Is decoding=async set, and is loading=lazy used only below the fold?
  • Is the file sized to the largest display size it will have, not larger?
  • Is the asset on a CDN with long cache and a versioned URL?
  • For CSS backgrounds, is there a preload and a fixed height or aspect ratio?
  • For framework components, did you check the emitted HTML on real pages?
  • For galleries, are thumbnails light and deferred, with larger images loaded on interaction?

Work one template at a time: /, /pricing, /product/widget, then /blog/*. Measure before and after for each template, not the whole site at once.

Questions

Sources

Check my site, free

Drop your homepage URL and get a free check: in about thirty seconds it reads your site and rivals on your searches, and shows three image fixes to ship first.

  • Free check, no card
  • Read-only, your own accounts
  • Readable by your agent

Read next