Blog · Performance & Optimization
Image Optimization Guide 2026: Faster Images, Better LCP
A complete guide to optimizing images — modern formats (AVIF/WebP), responsive srcset, preventing CLS, lazy loading vs the LCP image, compression, CDNs and placeholders — each with why and a code example.
Images are usually the heaviest thing on a page and the most common Largest Contentful Paint element — which makes them the single biggest lever for Core Web Vitals. Getting them right means the correct format, the right size for each device, reserving space to avoid layout shift, and loading the hero image fast while deferring the rest. Here is a complete image optimization guide for 2026, each technique with the reasoning and a code example.
1. Use modern formats: AVIF, then WebP, then a fallback
AVIF is typically ~30–50% smaller than JPEG at equal quality and WebP ~25–35% smaller. In 2026 WebP support is ~97% and AVIF ~92–93%, so it’s safe to ship AVIF first with a fallback. The browser picks the first <source> it supports, so order AVIF → WebP → <img>, and keep the <img> as the final child (that’s where alt, width and height live).
<picture>
<source srcset="hero.avif" type="image/avif">
<source srcset="hero.webp" type="image/webp">
<img src="hero.jpg" alt="Product hero" width="1200" height="675">
</picture>
2. Serve responsive images with srcset and sizes
A phone shouldn’t download a desktop-sized file. srcset + sizes lets the browser pick the smallest image that fills the slot at the current viewport and device pixel ratio. Critically, sizes must describe the image’s *rendered* width, not the file width — a wrong sizes (like always 100vw) defeats the whole mechanism.
<img src="photo-800.jpg"
srcset="photo-400.jpg 400w, photo-800.jpg 800w, photo-1600.jpg 1600w"
sizes="(max-width: 600px) 100vw, 50vw"
width="800" height="600" alt="…">
3. Always set width and height to prevent CLS
Without intrinsic dimensions the browser doesn’t reserve space, so content jumps when the image loads — a Cumulative Layout Shift hit. The width/height attributes give it the aspect ratio up front; keep height:auto in CSS so the image stays fluid *and* shift-free.
<img src="photo.jpg" width="1600" height="900" alt="…" style="width:100%;height:auto">
/* fluid images: preserve the box before load */
img { aspect-ratio: 16 / 9; width: 100%; height: auto; }
4. Lazy-load offscreen images
Native lazy-loading defers offscreen images until they approach the viewport, saving bandwidth and speeding the initial load with no JavaScript. The one rule: never lazy-load your LCP or above-the-fold image — that delays the very thing you’re scored on.
<img src="gallery-7.jpg" loading="lazy" width="600" height="400" alt="…">
5. Eager-load the LCP image with fetchpriority="high"
The hero/LCP image should be discovered and downloaded as soon as possible. fetchpriority="high" raises it above other resources — Google measured LCP dropping from 2.6s to 1.9s from this alone. Never combine it with loading="lazy"; the two contradict each other.
<img src="hero.avif" fetchpriority="high" width="1200" height="675" alt="…">
<!-- do NOT add loading="lazy" here -->
6. Preload the LCP image when it isn't discoverable early
If the LCP image is a CSS background-image, injected by JavaScript, or otherwise hidden from the preload scanner, preloading surfaces it in the critical path immediately. Only preload the *one* image that is actually the LCP — extra preloads steal bandwidth from it. For a plain in-markup <img>, fetchpriority is usually enough.
<link rel="preload" as="image" href="hero.avif"
fetchpriority="high"
imagesrcset="hero-800.avif 800w, hero-1600.avif 1600w"
imagesizes="100vw">
7. Compress with per-format quality tuning
Most images ship far larger than necessary; for photos, AVIF/WebP around quality 75–80 removes 30–70% of the bytes with no visible loss. Automate it with sharp in your build. Note AVIF’s quality scale differs from JPEG’s — AVIF ~50–60 often matches JPEG ~80, so tune per format rather than reusing one number.
import sharp from "sharp";
await sharp("in.jpg").resize(1600).avif({ quality: 55 }).toFile("out.avif");
await sharp("in.jpg").resize(1600).webp({ quality: 78 }).toFile("out.webp");
8. Serve appropriately sized images
Downscaling a huge source in the browser wastes bandwidth and decode time and can even look worse. Generate variants matched to real display sizes, accounting for high-DPR (2x/3x) screens — a 400 CSS-px slot wants an 800px asset for retina, not a 4000px original.
❌ <img src="photo-4000.jpg" style="width:400px">
✅ 400px slot -> generate 400w and 800w (2x) variants; let srcset choose
9. Use an image CDN for on-the-fly transforms
Image CDNs (Cloudflare Images, Fastly, imgix, Cloudinary) generate format/size/quality variants at request time, negotiate AVIF/WebP via the Accept header, and serve from edge caches — no manual variant pipeline. Ensure the cache keys on Accept/Vary so a WebP isn’t served to a client that asked for AVIF.
<img src="https://cdn.example.com/hero.jpg?w=800&format=auto&q=auto" alt="…">
10. Use SVG for icons and logos, optimized with SVGO
SVG is resolution-independent and tiny for flat art and UI — crisp at any DPR with no per-size variants — but design-tool exports carry editor cruft that SVGO strips. SVG is wrong for photographs (paths explode in size), and any user-uploaded SVG must be sanitized (XSS risk).
npx svgo icon.svg -o icon.min.svg # strips metadata, comments, redundant nodes
11. Replace images with CSS where you can
Gradients, solid fills, shadows and simple shapes as CSS cost zero image requests and scale perfectly — every image you don’t request is the cheapest optimization. Just watch that complex gradients or filters over large areas can be paint-expensive, so measure for full-viewport effects.
/* ❌ a 40 KB gradient JPG */
.hero { background: url("gradient.jpg"); }
/* ✅ zero bytes over the wire */
.hero { background: linear-gradient(135deg, #6366f1, #ec4899); }
12. Show a placeholder / blur-up (LQIP)
A tiny (~16px) blurred preview shown instantly gives perceived speed and prevents an empty box while the full image loads — an AVIF/WebP LQIP can be a few hundred bytes inlined as a data URI. Keep the placeholder reserving the correct aspect ratio so the swap causes no shift, and don’t inline large placeholders (that bloats the HTML).
// generate a ~16px LQIP with sharp
const lqip = await sharp("hero.jpg").resize(16).webp({ quality: 20 }).toBuffer();
// then inline it as a background data URI behind the real <img>
13. Strip metadata and cache immutably
EXIF/GPS/color-profile metadata can add tens of KB per photo and leaks location, and content-hashed filenames let you cache images immutably for a year so repeat views cost nothing. Don’t strip the ICC/color profile blindly — keep sRGB and drop only EXIF, GPS and thumbnails, or colors can wash out.
sharp("in.jpg").withMetadata({ icc: "srgb" }); // keep color profile, drop the rest
// Cache-Control: public, max-age=31536000, immutable (on hashed filenames)
14. Add decoding="async" and content-visibility for long lists
decoding="async" lets the browser decode an image off the main thread so it doesn’t block rendering of surrounding content, and for very long galleries or feeds, content-visibility: auto skips offscreen rows’ layout and paint entirely. Don’t put decoding="async" on the LCP image, where you want prioritized, synchronous paint.
<img src="thumb.avif" decoding="async" loading="lazy" width="300" height="200" alt="…">
.feed-row { content-visibility: auto; contain-intrinsic-size: auto 220px; }
Image optimization checklist
- Serve AVIF → WebP → JPEG via
<picture>; usesrcset/sizesfor the right size per device. - Set
width/height(oraspect-ratio) on every image to prevent CLS. - Lazy-load offscreen images; eager-load the LCP image with
fetchpriority="high"(preload if hidden). - Compress with per-format quality tuning; don’t ship oversized originals.
- Use an image CDN or a build pipeline (sharp) to generate variants.
- Use optimized SVG for icons; replace simple images with CSS.
- Add blur-up placeholders, strip metadata, cache immutably, and use
decoding="async".
Frequently asked questions
What image format should I use in 2026?
<picture>. In 2026 WebP has ~97% support and AVIF ~92–93%, so AVIF → WebP → legacy covers everyone while giving most users the smallest file.How do I stop images from causing layout shift?
width and height attributes (or a CSS aspect-ratio) so the browser reserves the correct space before the image loads. Keep height:auto in CSS so it stays fluid. Omitting the dimensions is a leading cause of Cumulative Layout Shift.Should I lazy-load all images?
loading="lazy" to save bandwidth, but never lazy-load the LCP or above-the-fold image — deferring it delays your Largest Contentful Paint. Keep the hero image eager and add fetchpriority="high".How much does image optimization improve LCP?
fetchpriority="high" alone.Prototype responsive images and lazy loading live in XCODX Studio. For the metric they most affect, see Improve Core Web Vitals, and for markup fundamentals, our HTML best practices.