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

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.
| Format | Typical size vs JPEG | Transparency | Animation | Browser support (2026) | Best for |
|---|---|---|---|---|---|
| AVIF | Typically 30–50% smaller | Yes | Yes | All current major browsers (Chrome 85+, Firefox 93+, Safari 16.4+) | Photos, hero and product images |
| WebP | Typically 25–35% smaller | Yes | Yes | All current major browsers | Photos and graphics; universal fallback |
| JPEG | Baseline | No | No | Universal | Last-resort fallback for photos |
| PNG | Often 2–5x larger for photos | Yes | Only as APNG | Universal | Lossless screenshots and UI graphics |
| SVG | Not comparable (vector); icons are often 1–5 KB | Yes | Yes (CSS or SMIL) | Universal | Logos, 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.
<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
cwebpdefault 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.


