Images account for ~50% of the average web page's byte weight according to HTTP Archive data from early 2026, yet most teams still ship oversized JPEGs without srcset or lazy loading. The good news: in 2026 the tooling handles most of this automatically — you just need to wire it up correctly.
What changed in 2026
- AVIF browser support is now universal — Chrome, Firefox, Safari 16+, and Edge all support AVIF. It's the primary format recommendation, not an experiment.
- Next.js Image, Nuxt Image, and Astro's built-in
<Image> component all default to AVIF output with WebP fallback and automatic srcset generation.
- Cloudflare Images and Vercel Image Optimization added AI-based content-aware cropping, removing the need for manual focal points.
sharp 0.33 dropped the libvips pre-build requirement on Apple Silicon — install is instant.
<img loading="lazy"> and fetchpriority="high" are now supported everywhere and should be set on every image.
Format comparison
| Format |
Typical size vs JPEG |
Browser support |
Use for |
| AVIF |
~50% smaller |
Universal (2026) |
Primary format |
| WebP |
~25–35% smaller |
Universal |
Fallback |
| JPEG |
Baseline |
Universal |
Legacy / no tooling |
| PNG |
Larger (lossless) |
Universal |
Transparent images |
| SVG |
Vector |
Universal |
Icons, logos |
| GIF |
Large |
Universal |
Replace with video |
Responsive images with srcset
<img
src="/images/hero-800.avif"
srcset="
/images/hero-400.avif 400w,
/images/hero-800.avif 800w,
/images/hero-1600.avif 1600w
"
sizes="(max-width: 600px) 100vw, (max-width: 1200px) 50vw, 800px"
alt="Dashboard screenshot"
loading="lazy"
decoding="async"
width="1600"
height="900"
/>
Always include width and height attributes — they prevent layout shift (CLS) by reserving space before the image loads.
Priority hints
<!-- Hero / LCP image: load immediately -->
<img src="/hero.avif" fetchpriority="high" loading="eager" ... />
<!-- Below fold: defer -->
<img src="/feature.avif" fetchpriority="low" loading="lazy" ... />
Build-time optimization with Sharp
// scripts/optimise-images.ts
import sharp from 'sharp'
import { glob } from 'glob'
const images = await glob('public/images/raw/**/*.{jpg,png}')
for (const file of images) {
const base = file.replace('/raw/', '/').replace(/\.(jpg|png)$/, '')
await sharp(file)
.resize({ width: 1600, withoutEnlargement: true })
.avif({ quality: 60 })
.toFile(`${base}.avif`)
await sharp(file)
.resize({ width: 1600, withoutEnlargement: true })
.webp({ quality: 75 })
.toFile(`${base}.webp`)
}
Vite plugin approach
// vite.config.ts
import { defineConfig } from 'vite'
import imagemin from 'vite-plugin-imagemin'
export default defineConfig({
plugins: [
imagemin({
avif: { quality: 60 },
webp: { quality: 75 },
svgo: { plugins: [{ removeViewBox: false }] },
}),
],
})
CDN-based transformation (Cloudflare Images)
# Resize to 800px wide, convert to AVIF automatically:
https://imagedelivery.net/<account>/<image-id>/w=800,format=avif
With Cloudflare, you store the original once and transform on-demand at the edge. No build step needed — the CDN caches each variant.
How to pick an approach
| Team size / setup |
Recommended approach |
| Next.js / Nuxt / Astro |
Use the framework's <Image> component |
| Any Vite project |
vite-plugin-imagemin + srcset |
| CMS-driven site |
Cloudflare Images, Imgix, or Sanity CDN |
| Static site, manual control |
Sharp build script |
Common mistakes
Missing width and height — without dimensions, the browser doesn't know how much space to reserve and you get layout shift.
One size fits all — serving a 2400 px image to a 375 px mobile viewport wastes ~10× the bytes. Use srcset.
Lazy-loading the LCP image — the Largest Contentful Paint image should be loading="eager" and fetchpriority="high". Only defer below-the-fold images.
Forgetting alt text — accessibility and SEO both require descriptive alt on content images. Empty alt="" is correct only for decorative images.
What to skip
- Progressive JPEG as a performance strategy — AVIF is just better; don't spend time on progressive encoding.
- GIFs for animations — replace with
<video autoplay loop muted playsinline> which is 5–10× smaller.
- Manual Photoshop export for every size — automate it; manual workflows break as soon as the project scales.
FAQ
What quality setting should I use for AVIF?
Quality 50–65 is the sweet spot for photos — visually indistinguishable from the original at typical web display sizes. Test with your specific imagery.
Should I use a <picture> element instead of <img srcset>?
Use <picture> when you want art direction (different crops per breakpoint) or need to serve different formats with explicit fallback. For format-only decisions, a modern CDN or framework component handles it automatically.
Does lazy loading hurt SEO?
No. Google crawls lazy-loaded images. Just ensure images meaningful to SEO have descriptive alt text and are not hidden behind JavaScript that search engines won't execute.
How do I measure current image waste?
Run Lighthouse (or npx unlighthouse) on your site — the "Efficiently encode images" and "Properly size images" audits show exactly which images are wasting bytes and by how much.
Where to go next