Images and Web Performance: How to Optimize Images for Core Web Vitals

Published May 2026 · Updated May 27, 2026

Why images are the #1 web performance problem

According to HTTP Archive data, images account for roughly half of the average web page's total byte weight. For pages with large hero images or photo galleries, that proportion can climb to 70–80%. Downloading a 1.5MB JPEG over a mobile connection takes considerably longer than downloading a 200KB WebP at comparable quality — and that delay shows up directly in your performance scores.

Images affect performance in two distinct ways. First, they add to total page weight, which increases the time before the page is usable on slow connections. Second, they affect specific performance signals that Google and browsers measure — particularly how quickly the page's main content appears (LCP) and whether the layout shifts during loading (CLS).

The good news is that image optimization has a very high return on effort. Fixing image issues — resizing to appropriate dimensions, converting to WebP, adding loading="lazy" — typically moves performance scores more than any other single category of change.

Core Web Vitals explained

Core Web Vitals are a set of metrics Google uses to measure user experience. They feed into Google's page experience ranking signals. There are three main metrics:

Largest Contentful Paint
LCP — loading performance
Time from page load to when the largest visible element is fully rendered. For most pages, this is a hero image. Target: under 2.5 seconds. Images are the leading cause of poor LCP scores.
Cumulative Layout Shift
CLS — visual stability
Measures how much content jumps around during loading. Images without explicit width and height attributes cause layout shift when they load. Target: CLS score under 0.1.
Interaction to Next Paint
INP — responsiveness
Time from user interaction to visual response. Images can indirectly affect INP if decoding large images blocks the main thread. Target: under 200ms. Less directly image-related than LCP and CLS.

1. Serve images at the right dimensions

The most common image performance problem is serving images that are much larger than they are displayed. A camera photo at 4000×3000 pixels displayed in a 800×600 slot is wasting bandwidth — the browser downloads a 3MB file and shrinks it to 800px wide using CSS. The user sees no difference, but they waited for 2.5MB of extra data to download.

To check: open browser DevTools, inspect an image, and compare the natural (intrinsic) size to the displayed size. If a 1920px image is displayed at 400px, you are downloading roughly 23× more data than needed.

The srcset attribute for responsive images

Different devices have different screen widths and pixel densities. The srcset attribute lets you provide multiple image files and let the browser pick the appropriate one:

<img
  src="/images/hero-800.webp"
  srcset="/images/hero-400.webp 400w,
          /images/hero-800.webp 800w,
          /images/hero-1200.webp 1200w"
  sizes="(max-width: 600px) 100vw, 800px"
  alt="Description of the hero image"
  width="800"
  height="500"
>

The sizes attribute tells the browser how wide the image will be displayed at different viewport sizes, so it can select the most appropriate source from srcset before making the network request.

2. Format and compression

Once you have the right dimensions, the format and compression level determine the actual file size. The modern web stack recommendation is:

For most hero images, the target file size should be under 150KB. For inline content images, under 100KB. For thumbnails, under 50KB. These are guidelines, not hard limits — a full-screen background image can be larger if it is loaded correctly (see preloading below).

Quick win: Run your existing JPEG images through the image compressor and convert them to WebP. For most sites, this alone reduces image weight by 30–40% with no visible quality change.

3. Lazy loading

By default, browsers begin loading all <img> elements in the page as soon as they parse the HTML, even images that are far below the visible area. For a long page with 20 images, this means loading content the user may never scroll to.

The loading="lazy" attribute tells the browser to defer loading images until they are near the viewport:

<img src="thumbnail.webp" alt="Product thumbnail" loading="lazy" width="200" height="200">

This is one of the highest-return, lowest-effort performance improvements you can make. Add it to all images except the one most likely to be the LCP element (typically the hero or first above-the-fold image). Lazy loading the LCP image would delay it and hurt your LCP score.

4. Width and height attributes to prevent layout shift

When a browser encounters an <img> element without explicit width and height attributes, it does not know how much space to reserve for it. The image loads, appears, and pushes surrounding content down — this is layout shift, and it is what the CLS metric measures.

Adding width and height attributes that match the image's aspect ratio allows the browser to reserve the correct space before the image downloads:

<img src="photo.webp" alt="Description" width="800" height="450">

You do not need to use these as fixed pixel dimensions in the layout — CSS can override the visual size. The important thing is that the ratio is correct so the browser can reserve proportional space. An 800×450 image and a 400×225 image have the same 16:9 ratio; either set of attributes will prevent CLS.

5. Preloading the LCP image

For your most important image — typically the hero or banner at the top of the page — you can ask the browser to start downloading it immediately, even before it processes the full HTML. Use a <link rel="preload"> tag in the <head>:

<link
  rel="preload"
  as="image"
  href="/images/hero-800.webp"
  imagesrcset="/images/hero-400.webp 400w, /images/hero-800.webp 800w"
  imagesizes="(max-width: 600px) 100vw, 800px"
>

Preloading the LCP image is particularly effective when the image is referenced in CSS (as a background-image), which the browser cannot discover until after it has downloaded and parsed the stylesheet. If the image is in an <img> tag near the top of the HTML, preloading is less critical but still helpful.

6. Responsive images with the picture element

The <picture> element allows you to provide different image formats and let the browser pick the best one it supports:

<picture>
  <source type="image/avif" srcset="hero.avif">
  <source type="image/webp" srcset="hero.webp">
  <img src="hero.jpg" alt="Hero image description" width="800" height="450">
</picture>

Browsers that support AVIF will use the AVIF file. Browsers that support WebP (but not AVIF) will use the WebP file. All others fall back to JPEG. This approach gives you the best compression where it is supported without breaking compatibility.

7. Decoding attribute

Large images can take time for the browser to decode (decompress and render). The decoding="async" attribute tells the browser it does not need to finish decoding the image before rendering the rest of the page:

<img src="large-photo.webp" alt="Description" loading="lazy" decoding="async" width="1200" height="800">

For below-the-fold images, this prevents image decoding from blocking the main thread. For the LCP image, omit this attribute (or use decoding="sync") since you want it decoded as quickly as possible.

Practical audit checklist

Work through this checklist to audit the images on any web page:

  1. 1Open Chrome DevTools → Lighthouse and run a performance audit. Note which images are flagged under "Properly size images," "Serve images in modern formats," and "Efficiently encode images."
  2. 2For each flagged image, check the natural (intrinsic) dimensions vs. the displayed dimensions in the Elements panel. If the image is more than 2× larger than its display size, create a correctly sized version.
  3. 3Convert all JPEG and PNG images to WebP (or AVIF where supported). Use the format converter. Aim for 25–35% file size reduction per image.
  4. 4Compress images that are still over their target file size (200KB for hero images, 100KB for content images, 50KB for thumbnails). Use the image compressor.
  5. 5Add loading="lazy" to all <img> elements except the LCP image (usually the first hero or banner image).
  6. 6Add explicit width and height attributes to every <img> element, matching the image's natural aspect ratio. This prevents Cumulative Layout Shift.
  7. 7Add a <link rel="preload" as="image"> tag in <head> for the LCP image. This is especially important if the image is set via CSS background-image.
  8. 8Re-run the Lighthouse audit. Most image-related findings should now be resolved. Address any remaining issues, then check the LCP score specifically — it should have improved.

FAQ

What is the LCP element, and how do I find it?
The LCP element is the largest visible element in the viewport when the page first loads. To find it: open Chrome DevTools, go to the Performance panel, record a page load, and look for the LCP marker in the timeline. Alternatively, run a Lighthouse audit — it reports the LCP element directly. On most landing pages, it is the hero image or a large heading.
Does lazy loading hurt SEO?
No, for standard HTML loading="lazy" on <img> elements. Googlebot renders pages and respects native lazy loading — it scrolls through the page to trigger lazy-loaded content. The exception is JavaScript-based lazy loading using IntersectionObserver that requires JavaScript to load images: Googlebot may not trigger all of those. Native browser lazy loading (loading="lazy") is safe for SEO.
How does a CDN help with image performance?
A CDN (Content Delivery Network) serves your images from servers geographically close to each visitor, reducing download latency. Some CDNs (Cloudflare Images, Imgix, Cloudinary) also provide on-the-fly image resizing, format conversion, and compression — so you upload one master file and the CDN serves the right format and size for each request. This is the most scalable approach, but it involves cost and setup complexity that a CDN like Cloudflare Pages does not provide out of the box.
My LCP image is set as a CSS background-image. How do I optimize it?
CSS background images are discovered late in the loading process — the browser cannot know about them until it has downloaded and parsed the CSS file. Use a <link rel="preload" as="image" href="..."> in the <head> to tell the browser to start fetching the image early. If possible, switch to an <img> element in the HTML instead — it is discovered earlier and can be preloaded via <link rel="preload"> with fetchpriority="high".
Is there a target file size that guarantees a good LCP score?
No single file size guarantees a good LCP score because LCP is also affected by server response time, render-blocking resources, and connection speed. But as a practical guide: a hero image under 100KB on a reasonably fast server will rarely be the bottleneck on a 4G connection. Under 50KB is even safer. The LCP threshold is 2.5 seconds — everything that runs before the image renders (HTML parsing, CSS, JavaScript) also counts toward that budget.

Related tools

Other guides