// Performance
How to Optimize Images for Web: Formats, Compression, and Lazy Loading
A complete guide to web image optimization. Covers modern formats (WebP, AVIF), compression settings, lazy loading, responsive images with srcset, and CDN delivery — with specific tools for each step.
Why images matter more than anything else for performance
Images typically account for 50–75% of a web page's total download size. They're the single biggest contributor to slow load times, high LCP scores, and mobile data consumption. Unlike JavaScript bundles or CSS files, images often get loaded with no optimization strategy at all — uploaded at full resolution from a camera or design tool and served raw to every visitor.
A single unoptimized hero image can be 3–5MB and cause your Largest Contentful Paint to fail Core Web Vitals on mobile. The same image, properly optimized, might weigh 80–150KB — a 20x reduction with no perceptible quality difference to a visitor on a phone screen. No other optimization technique offers this magnitude of improvement for this little effort.
Image optimization has two dimensions: reducing file size without degrading perceived quality, and delivering the right image to the right context. A 2400px image served to a 400px phone screen wastes bandwidth regardless of compression. This guide covers both dimensions.
Modern image formats: WebP and AVIF
JPEG and PNG were designed in the 1990s. WebP (developed by Google in 2010) achieves 25–35% smaller files than JPEG at equivalent perceived quality for photographs, and significantly better compression than PNG for images with transparency. AVIF (a newer format based on the AV1 video codec) achieves another 20–30% reduction on top of WebP, at the cost of slightly slower encoding.
Both WebP and AVIF are now supported by all modern browsers (Chrome, Firefox, Safari 14+, Edge). The correct implementation strategy is to serve AVIF to browsers that support it, WebP to browsers that support WebP but not AVIF, and JPEG/PNG as a fallback for older browsers. The HTML picture element handles this natively: list sources in order of preference, and the browser downloads only the first format it supports.
You don't need to manually convert images for most workflows. CDNs like Cloudflare Images, Cloudinary, and imgix automatically serve the optimal format for each browser. WordPress plugins like ShortPixel, Imagify, and EWWW Image Optimizer handle bulk conversion. For manual conversion, Squoosh (squoosh.app) is a free browser-based tool that shows quality/file size tradeoffs in real time.
- Use the picture element with AVIF, WebP, and JPEG/PNG sources in that order
- Or use a CDN/image service that handles format negotiation automatically
- For WebP, target quality 75–85 for photos, 80–90 for illustrations
- For AVIF, quality 50–65 is typically visually equivalent to JPEG quality 85
Compression: finding the right quality setting
Image compression reduces file size by discarding information the human eye is unlikely to notice. Lossy compression (used by JPEG, WebP, AVIF in photo mode) permanently removes data to achieve smaller files. Lossless compression (used by PNG, GIF, and WebP/AVIF lossless modes) reorganizes data more efficiently without loss.
For photographs, lossy compression at quality 75–85 (on a 0–100 scale) is typically indistinguishable from higher quality settings on a calibrated monitor, let alone a phone screen. The difference between quality 85 and quality 95 adds 30–50% to file size with minimal visible benefit. Test by downloading both versions and comparing at 100% zoom on your best monitor.
For flat graphics, icons, and illustrations with large areas of solid color, lossless WebP typically beats JPEG compression significantly. PNG is usually the right choice for images with transparency that need to remain crisp — but run them through a lossless compressor like OptiPNG or TinyPNG before serving.
Related reading:
Responsive images: serving the right size
Sending a 2400px-wide image to a 375px iPhone screen wastes 97% of the pixels. The browser scales it down visually, but it still downloads the full file. Responsive images fix this by providing multiple source files and letting the browser select the appropriate one based on screen width and pixel density.
The srcset attribute lists image URLs alongside their widths. The sizes attribute describes the rendered size of the image at different viewport widths. The browser uses both to choose the best file. A typical implementation provides sizes at 400px, 800px, 1200px, and 1600px, covering mobile through large desktop.
For modern frameworks, this is often handled automatically. Next.js's Image component generates srcset and serves appropriately sized images from a built-in optimization API. WordPress 5.0+ generates srcset for uploaded images automatically. If your CMS doesn't handle this, Cloudinary and similar services can generate responsive versions via URL parameters.
- Provide srcset with at least 3 size variants: ~400px, ~800px, ~1200px
- Set sizes attribute to describe the image's rendered width at each breakpoint
- Use width and height attributes on every img to prevent layout shift
- For full-width hero images, consider a dedicated 320px and 640px version for mobile
Lazy loading: deferring off-screen images
Lazy loading delays image downloads until the image is about to scroll into view. A page with 20 images that implements lazy loading only downloads the images visible in the viewport on initial load — typically 2–4 images — and loads the rest on demand as the user scrolls.
The native HTML loading='lazy' attribute implements lazy loading without JavaScript. It's supported by all modern browsers and requires no library or framework. Simply add loading='lazy' to every img element that is below the fold — not visible on initial page load without scrolling.
Never lazy-load your hero image or any image that appears above the fold. These images are needed immediately for rendering. Lazy-loading them causes a blank placeholder to flash, worsens LCP, and provides no benefit since they'd be downloaded immediately regardless. For the hero or LCP image, add fetchpriority='high' and consider preloading it in the document head.
CDN delivery and long-term caching
A Content Delivery Network (CDN) stores copies of your images at servers distributed globally. When a visitor in Sydney requests an image from your server in Virginia, a CDN can serve it from a Sydney edge node — reducing latency by 50–300ms and avoiding transcontinental data transfer.
Set long cache-control headers for images: Cache-Control: public, max-age=31536000, immutable for fingerprinted images (where the filename changes when the content changes). This tells browsers to serve the image from local cache for up to one year without checking the server, making repeat page loads nearly instant for returning visitors.
WebEnture's Image Optimization Agent and Page Weight Agent audit your entire site's image inventory: identifying uncompressed images, missing dimensions, missing lazy loading, images served from the origin instead of a CDN, and cache header misconfigurations. The Performance Agent ties these findings to your Core Web Vitals metrics so you can prioritize by LCP impact.