Image optimization for web performance: AVIF, WebP, responsive images and lazy loading

A large camera photo being compressed into smaller AVIF and WebP files at several widths, with arrows pointing to a phone, a laptop and a desktop screen

Image optimization means serving every image in the smallest format, resolution and file size that still looks right on the visitor's screen, and loading it at the right moment. In practice that means AVIF or WebP instead of JPEG and PNG, responsive srcset candidates instead of one huge file, explicit width and height to prevent layout shift, and lazy loading for everything except the main above-the-fold image. Done well, it often cuts total page weight in half and directly improves Largest Contentful Paint (LCP).

Why images dominate page weight

On most content, news and e-commerce pages, images are the largest share of transferred bytes, often more than JavaScript, CSS and fonts combined. The reason is simple: a 12-megapixel phone photo is typically 2–5 MB as a JPEG, and it frequently ends up on a page where it is displayed 1,200 pixels wide. At that size, a well-compressed AVIF or WebP version usually weighs 100–250 KB. Everything in between is wasted bandwidth.

Images also decide how fast the page feels. On many pages the LCP element is an image, such as a hero banner, product photo or article cover. Google considers LCP good at 2.5 seconds or less for the 75th percentile of real visitors, and a 1.5 MB hero image alone can blow that budget on a mid-range phone. Our Core Web Vitals guide covers LCP, INP and CLS in depth.

Distance makes heavy images worse. A new connection starts with a small congestion window (typically about 14 KB) that roughly doubles every round trip, so a 1 MB file needs around seven round trips before it is fully delivered. At 20 ms per round trip that is about 140 ms; from a region 180 ms away it is well over a second. Every kilobyte you remove shortens the wait most for your most distant visitors.

AVIF vs WebP vs JPEG vs PNG vs SVG: choosing a format

Pick the format by image type first, then by browser support. All the modern raster formats below are safe in 2026 as long as you keep a fallback for the rare old browser.

FormatTypical size vs JPEGTransparencyAnimationBrowser support (2026)Best for
AVIFTypically 30–50% smallerYesYesAll current major browsers (Chrome 85+, Firefox 93+, Safari 16.4+)Photos, hero and product images
WebPTypically 25–35% smallerYesYesAll current major browsersPhotos and graphics; universal fallback
JPEGBaselineNoNoUniversalLast-resort fallback for photos
PNGOften 2–5x larger for photosYesOnly as APNGUniversalLossless screenshots and UI graphics
SVGNot comparable (vector); icons are often 1–5 KBYesYes (CSS or SMIL)UniversalLogos, icons, simple illustrations

A practical default: AVIF first, WebP second, JPEG as the final fallback for photos; SVG for logos and icons; lossless WebP or PNG only for screenshots where every pixel matters. AVIF encodes several times slower than WebP, so generate it at build time or let an image CDN cache it rather than encoding on every request. Check current support on caniuse.com if your audience skews toward older devices.

Replace animated GIFs. A GIF is often several times larger than the same clip as an MP4 or WebM video (use a muted, looping, autoplaying <video>) or as an animated WebP or AVIF.

For SVG, run files through an optimizer such as SVGO to strip editor metadata, and never embed a base64 photo inside an SVG: you get the weight of a raster image without any of the benefits of vector graphics.

Compression quality settings that look the same but weigh less

Quality numbers are not comparable across formats or encoders, but these starting points produce files that most people cannot tell apart from the original at normal viewing size:

  • JPEG (MozJPEG): quality 70–80, progressive encoding. Above 85, the file grows quickly with almost no visible gain.
  • WebP: quality 70–80 (the cwebp default is 75).
  • AVIF: quality 45–60 on the 0–100 scale used by libavif and sharp (sharp defaults to 50). AVIF holds up well at low settings; it smooths fine texture rather than producing blocky artifacts.
  • Lossless: only for screenshots, diagrams and UI graphics with flat colors and sharp edges.

Resize first, then compress

Resizing saves far more than quality tweaks. A 4,000-pixel-wide photo saved at quality 60 is still much heavier than a 1,600-pixel version at quality 80. Always start from the highest-quality master, resize to the widths you actually serve, then encode once. Recompressing an already-compressed JPEG stacks artifacts on artifacts.

Strip metadata and check visually

Camera EXIF data, embedded thumbnails and editing history can add tens of kilobytes per image and may leak GPS coordinates. Strip them, but keep the color profile (or convert to sRGB) so colors stay accurate. Then compare the output against the original at 100% zoom on a high-density screen. Product photos, skin tones and images with small text usually need a few more quality points than landscapes.

Responsive images with srcset, sizes and the picture element

A single 1,600-pixel image is right for a large desktop screen and roughly four times too many pixels for a phone. Responsive images let the browser choose the best candidate for the viewport width and pixel density.

srcset and sizes for resolution switching

List each file with its real pixel width (w descriptor) in srcset, and tell the browser how wide the image will be displayed with sizes. The browser multiplies the slot width by the device pixel ratio and picks the smallest file that covers it.

<img src="/img/team-800.jpg"
     srcset="/img/team-480.jpg 480w,
             /img/team-800.jpg 800w,
             /img/team-1200.jpg 1200w,
             /img/team-1600.jpg 1600w"
     sizes="(max-width: 700px) 100vw, 700px"
     width="1600" height="900"
     alt="Our support team in the Seoul office"
     loading="lazy" decoding="async">

Three to five widths are enough for most layouts; 480, 800, 1200 and 1600 pixels cover phones through 2x desktop screens. Get sizes right: if you leave it out, the browser assumes 100vw and a thumbnail in a three-column grid downloads a full-width file.

The picture element for modern formats

Wrap the image in <picture> to offer AVIF and WebP with a JPEG fallback. The browser uses the first <source> whose type it supports. The inner <img> is still the real element: it carries alt, dimensions, loading attributes and the fallback file.

<picture>
  <source type="image/avif" sizes="100vw"
          srcset="/img/hero-800.avif 800w, /img/hero-1200.avif 1200w, /img/hero-1600.avif 1600w">
  <source type="image/webp" sizes="100vw"
          srcset="/img/hero-800.webp 800w, /img/hero-1200.webp 1200w, /img/hero-1600.webp 1600w">
  <img src="/img/hero-1200.jpg" sizes="100vw"
       srcset="/img/hero-800.jpg 800w, /img/hero-1200.jpg 1200w, /img/hero-1600.jpg 1600w"
       width="1600" height="700" alt="Mountain trail at sunrise"
       fetchpriority="high" decoding="async">
</picture>

For art direction, such as a square crop on phones and a wide crop on desktop, add a media attribute to a <source> and give that source its own width and height. The MDN img reference documents every attribute.

Explicit width and height prevent layout shift (CLS)

When an image without dimensions finishes loading, everything below it jumps down. That movement counts toward Cumulative Layout Shift, which Google rates good at 0.1 or less. The fix costs nothing: set width and height attributes to the image's intrinsic size (or any values with the same ratio). Browsers turn them into an aspect-ratio and reserve the space before a single byte arrives. Keep the image fluid with CSS:

img, video {
  max-width: 100%;
  height: auto;
}
/* Fixed-ratio slots for cropped thumbnails */
.card-thumb {
  width: 100%;
  aspect-ratio: 4 / 3;
  object-fit: cover;
}

Use CSS aspect-ratio for containers that hold background images, embeds or ads, which have no attributes to read. A lazy-loaded image without dimensions is the worst case: it shifts the layout exactly while the visitor is scrolling and reading.

Lazy loading, fetchpriority and decoding done right

Native lazy loading for below-the-fold images

Add loading="lazy" to images that start outside the first viewport: gallery items, article images further down, footer logos. The browser defers them until the visitor scrolls near them. Chrome starts fetching when a lazy image comes within roughly 1,250 pixels of the viewport on fast connections and 2,500 pixels on slower ones, so visitors rarely see a blank box (see web.dev on browser-level lazy loading). The attribute works in every major browser (and on <iframe> embeds), so JavaScript lazy-loading libraries are no longer needed.

Why you should never lazy-load the LCP image

A lazy image cannot start downloading until the browser has built the layout and confirmed the image is near the viewport. For the hero image, that delay lands directly on LCP, often adding several hundred milliseconds. Many CMS themes and plugins add loading="lazy" to every image automatically, so check that your hero, logo and anything visible on first paint are excluded. For the same reason, avoid CSS background images for the LCP element: the browser discovers them only after the stylesheet is downloaded and parsed.

fetchpriority="high" for the hero image

Browsers start images at low priority until layout shows they are visible. fetchpriority="high" on the LCP image (as in the picture example above) moves it ahead in the queue immediately, and it is supported in all major browsers. Use it on one image, two at most: if everything is high priority, nothing is. When the hero has to be a CSS background or is inserted by JavaScript, preload it instead, as explained in web.dev's Fetch Priority guide:

<link rel="preload" as="image" fetchpriority="high"
      type="image/avif" href="/img/hero-1200.avif"
      imagesrcset="/img/hero-800.avif 800w, /img/hero-1200.avif 1200w, /img/hero-1600.avif 1600w"
      imagesizes="100vw">

decoding="async" for everything else

decoding="async" tells the browser it may paint other content without waiting for this image to be decoded. The gain is small but free, and it shows up mostly with large images on low-end phones.

Image CDNs and command-line tools

Image CDNs and on-the-fly resizing

An image CDN resizes, compresses and converts images on request, then caches the result at the edge. You upload one high-quality master and request variants through the URL. Most services pick AVIF or WebP automatically from the browser's Accept header. With Cloudflare, for example, a transformed URL looks like this:

/cdn-cgi/image/width=800,quality=75,format=auto/uploads/team-photo.jpg

Limit the widths you allow (for example 480, 800, 1200 and 1600) so the cache is not fragmented into hundreds of near-identical variants, and keep an eye on transformation billing. Because transformed images are served from edge locations near the visitor, they also shorten the distance every byte travels; our guide to how CDNs work explains why that matters worldwide.

Command-line tools: cwebp, avifenc, sharp and Squoosh

For build pipelines, cwebp (from libwebp) and avifenc (from libavif) are the reference encoders. avifenc does not resize, so feed it an already resized image:

# WebP at quality 75, resized to 1200 px wide (0 keeps the aspect ratio)
cwebp -q 75 -resize 1200 0 hero.jpg -o hero-1200.webp

# AVIF at quality 55, encoder speed 6 (0 = slowest and smallest, 10 = fastest)
avifenc -q 55 -s 6 hero-1200.jpg hero-1200.avif

In Node.js projects, sharp generates every width and format in one script and strips metadata by default:

// resize.mjs  (npm install sharp, then: node resize.mjs)
import sharp from 'sharp';

for (const width of [480, 800, 1200, 1600]) {
  const img = sharp('hero.jpg').resize({ width, withoutEnlargement: true });
  await img.clone().avif({ quality: 50 }).toFile(`hero-${width}.avif`);
  await img.clone().webp({ quality: 75 }).toFile(`hero-${width}.webp`);
  await img.clone().jpeg({ quality: 78, mozjpeg: true }).toFile(`hero-${width}.jpg`);
}

Squoosh (squoosh.app) compares formats and quality settings side by side in the browser; its old command-line package is unmaintained, so automate with sharp or the native encoders.

Measuring image savings with the 8-region speed test

Run a free 8-region speed test before and after optimizing, and compare these numbers:

  • Page weight: the median bytes transferred on the first visit across all regions, plus the file count. The report flags pages above 2.5 MB as heavy, and images are usually the first place to cut.
  • Transfer column: first-visit bytes per region. They should be nearly identical everywhere; if one region transfers much more, check whether a CDN or image service serves it a different variant.
  • Far-region load time: compare the origin region (fastest TTFB) with the farthest one. Image weight does not change DNS, TCP, TLS or server wait. It shows up in the page-load segment of the timeline, and that segment grows fastest in distant regions because every extra round trip costs more there. A smaller gap between your fastest and slowest region after optimizing means the savings are real.
  • Repeat visit: cached images should push repeat transfer close to zero. If repeat bytes stay high, images are being downloaded again; fix the headers with our browser caching guide.

The test measures full page load in headless Chromium from AWS data centers, not LCP from real users, so pair it with field data from Chrome UX Report or Search Console for Core Web Vitals. The testing methodology explains exactly what each number measures.

Frequently asked questions

Is AVIF better than WebP?

For photos, usually yes. AVIF files are often 20 to 30 percent smaller than WebP at the same visual quality, but they take longer to encode. Serve AVIF first with a WebP or JPEG fallback using the picture element.

Should I lazy-load all images?

No. Lazy-load images below the fold, but never the LCP image or anything visible in the first viewport, because lazy loading holds the request back until layout is known. Give the hero image fetchpriority high instead.

What quality setting should I use for WebP and AVIF?

Start around 75 for WebP and 50 to 60 for AVIF on a 0 to 100 scale, then compare against the original at 100 percent zoom. Product photos and images with fine text may need a few points more.

Do width and height attributes still matter with responsive CSS?

Yes. Browsers use the width and height attributes to reserve the correct aspect ratio before the image arrives, which prevents layout shift. Keep height set to auto in CSS so the image still scales with its container.

How do I know if images slow my site down in other countries?

Run a multi-region test and compare the transferred bytes and load time in the farthest regions with the origin region. Heavy pages need more round trips, and round trips cost more far away, so oversized images widen the gap between your fastest and slowest region.

DevTeam Engineering builds and runs Global Website Speed Test, a free tool that measures page speed from 8 AWS regions at once. We publish guides based on what we see in thousands of multi-region tests. How we test.

Related guides

Three gauge dials labeled LCP, INP and CLS with their needles in the green zone, above a browser window and a world map
Core Web Vitals

Core Web Vitals explained: how to improve LCP, INP and CLS

Core Web Vitals explained: what LCP, INP and CLS measure, the good thresholds at the 75th percentile, and the practical fixes that move each metric to green.

9 min read · Updated Oct 4, 2026
World map with one origin server in Virginia and CDN edge servers in London, Frankfurt, Singapore, Tokyo and Sydney answering nearby visitors from cache
CDN & Edge

What a CDN is and how it makes your website fast worldwide

A CDN serves files and cached HTML from edge servers near each visitor. Learn cache hits, keys, TTLs, purging and headers, and how a CDN cuts far-region TTFB.

9 min read · Updated Oct 4, 2026
A browser window pulling CSS, JavaScript, font and image files from a local cache drawer instead of a distant server, with a stopwatch showing a faster second visit
Caching

Cache-Control and browser caching: how to make repeat visits instant

Browser caching: set Cache-Control headers by file type, use immutable and stale-while-revalidate, bust caches with content hashes and speed up repeat visits.

9 min read · Updated Oct 4, 2026