Images account for 50%+ of most page weight. Learn how to choose the right format, compress efficiently, and implement lazy loading to dramatically improve Core Web Vitals.
According to HTTP Archive data, images represent 51% of the average page weight. On a typical 2MB page, over 1MB is images. This directly impacts:
The good news: image optimization is one of the highest-ROI performance improvements you can make, and modern tooling makes it straightforward.
AVIF (AV1 Image File Format) offers 30-50% better compression than WebP at the same visual quality. Browser support is now excellent (95%+ globally as of 2025).
<picture>
<source srcset="hero.avif" type="image/avif">
<source srcset="hero.webp" type="image/webp">
<img src="hero.jpg" alt="Hero image" width="1200" height="630">
</picture>
WebP provides 25-34% better compression than JPEG for photos and 26% better than PNG for graphics. Supported in all modern browsers. Use as a fallback for AVIF.
SVGs are infinitely scalable, tiny in file size, and can be animated with CSS. Never use PNG or JPEG for logos, icons, or diagrams.
<!-- ❌ Wrong: raster icon -->
<img src="logo.png" width="48" height="48">
<!-- ✅ Correct: vector icon -->
<img src="logo.svg" width="48" height="48">
<!-- Or inline SVG for CSS control -->
| Use Case | Recommended Format | Fallback |
|---|---|---|
| Photos | AVIF | WebP → JPEG |
| UI graphics, screenshots | WebP | PNG |
| Logos, icons, illustrations | SVG | WebP |
| Animated images | WebP (animated) | GIF |
| Transparent graphics | WebP | PNG |
Quality 80-85 is almost indistinguishable from quality 100 to the human eye, but results in 60-70% smaller files. For hero images and LCP candidates, quality 75-80 is a good target.
# Using sharp (Node.js)
sharp("input.jpg")
.resize(1200)
.webp({ quality: 80 })
.toFile("output.webp")
# Using ImageMagick
magick input.jpg -quality 80 output.webp
# Using cwebp (Google WebP tools)
cwebp -q 80 input.jpg -o output.webp
npx @squoosh/cli --avif auto --webp auto --resize "{width: 1200}" images/**/*.jpg
srcsetDon't serve a 2400px image to a 375px mobile screen. The srcset attribute lets the browser pick the right size:
<img
src="hero-800.webp"
srcset="
hero-400.webp 400w,
hero-800.webp 800w,
hero-1200.webp 1200w,
hero-1600.webp 1600w"
sizes="
(max-width: 640px) 100vw,
(max-width: 1024px) 80vw,
1200px"
alt="Hero image"
width="1200"
height="630"
>
Always specify width and height — this allows the browser to reserve layout space before the image loads, eliminating CLS.
<!-- Native lazy loading — supported everywhere -->
<img src="below-fold.webp" loading="lazy" alt="..." width="800" height="400">
<!-- Never lazy-load the LCP image! -->
<img src="hero.webp" loading="eager" fetchpriority="high" alt="...">
Avoid lazy-loading above-the-fold images. The LCP image should have loading="eager" (or omit loading entirely) and fetchpriority="high" to ensure it loads as fast as possible.
Next.js's <Image> component handles all of this automatically:
import Image from "next/image"
export function Hero() {
return (
<Image
src="/hero.jpg"
alt="Hero"
width={1200}
height={630}
priority // Preloads this image — use for LCP
quality={80}
placeholder="blur"
/>
)
}
srcset for responsive sizes/api/image with caching headersloading="lazy" on all below-fold imagesfetchpriority="high" on the LCP imagewidth and height attributessrcset + sizes for responsive imagesCache-Control headersNeed to quickly compress images for your project? Use HeoLab's Image Compressor to reduce file sizes in-browser without uploading to a server.
Image optimization is the single most impactful performance improvement for most websites. Start by converting to AVIF/WebP, add explicit dimensions to prevent CLS, lazy-load below-fold content, and prioritize your LCP image. These changes alone can cut page weight by 50% and move your LCP score from poor to good.