Images account for roughly half of the total bytes on the average web page. I audited a client's e-commerce site last year and found that switching from uncompressed PNGs to properly compressed WebP images reduced page weight from 8.2MB to 1.4MB. Load time on mobile dropped by 3.7 seconds. No layout changes. No redesign. Just compression. Understanding how image formats work and when to use each one is the single highest-impact performance optimization most sites are leaving on the table.
Lossy vs lossless: the fundamental trade-off
Lossless compression reduces file size without discarding any data. The decompressed image is identical to the original, pixel for pixel. PNG and GIF use lossless compression.
Lossy compression discards data that the algorithm considers visually unimportant. The decompressed image is an approximation of the original. JPEG, WebP (lossy mode), and AVIF use lossy compression.
The key insight is that human visual perception has known limitations. We are bad at noticing small changes in color compared to changes in brightness. We are bad at noticing fine detail in complex, textured areas. Lossy compression exploits these limitations to discard data we will not miss.
A JPEG at quality 85 is typically 60-80% smaller than the equivalent PNG, and in most cases you cannot tell the difference without zooming in and comparing pixel by pixel. At quality 60, artifacts become noticeable in gradients and sharp edges but are still acceptable for thumbnails and background images.
Format guide
JPEG
Best for: photographs, images with smooth gradients, images where file size matters more than pixel-perfect accuracy.
JPEG divides the image into 8x8 pixel blocks, converts each block to frequency components using a Discrete Cosine Transform (DCT), and quantizes the high-frequency components. The quality parameter controls how aggressively the high frequencies are discarded.
# ImageMagick: compress to quality 80
convert input.png -quality 80 output.jpg
# Sharp (Node.js)
sharp("input.png").jpeg({ quality: 80 }).toFile("output.jpg");
JPEG does not support transparency. If you need transparency, you need PNG, WebP, or AVIF.
The quality sweet spot for most web images is 75-85. Below 75, blocking artifacts become visible. Above 85, the file size increases faster than the visual quality improves. At quality 100, JPEG is barely compressed and often larger than PNG.
PNG
Best for: screenshots, diagrams, logos, images with text, images requiring transparency, images with sharp edges and flat colors.
PNG uses lossless compression (DEFLATE algorithm). It supports full alpha transparency (256 levels of opacity per pixel). It excels at images with large areas of uniform color because the compression algorithm exploits repetition.
A photograph saved as PNG will be 3-10x larger than the same photograph as JPEG, with no visual benefit. A screenshot with flat colors and text, conversely, will look sharp as PNG and blurry as JPEG.
PNG optimization is about reducing the file size without changing the visual output:
# pngquant: lossy PNG compression (reduces to 256 colors)
pngquant --quality=65-80 input.png -o output.png
# optipng: lossless optimization
optipng -o5 input.png
pngquant is technically lossy (it reduces the color palette to 256 colors), but for most UI screenshots and diagrams, the difference is imperceptible and the size reduction is 60-80%.
WebP
Best for: almost everything on the web. WebP supports both lossy and lossless compression, transparency, and animation.
WebP lossy produces files 25-35% smaller than equivalent-quality JPEG. WebP lossless produces files 20-30% smaller than equivalent PNG. Browser support is now universal (all modern browsers since 2020).
# cwebp (Google's encoder)
cwebp -q 80 input.png -o output.webp
# Sharp (Node.js)
sharp("input.png").webp({ quality: 80 }).toFile("output.webp");
The only reason not to use WebP everywhere is compatibility with non-browser contexts (email clients, some native apps, older image editing software).
AVIF
Best for: maximum compression when you can afford the encoding time.
AVIF is based on the AV1 video codec and produces files 30-50% smaller than WebP at equivalent quality. The trade-off is encoding speed: AVIF is 10-100x slower to encode than WebP. Decoding is also slower, though modern browsers handle it well.
# avifenc (libavif)
avifenc --min 20 --max 40 input.png output.avif
# Sharp (Node.js)
sharp("input.png").avif({ quality: 50 }).toFile("output.avif");
Browser support is strong (Chrome, Firefox, Safari since 16.4) but not as universal as WebP. Use AVIF with WebP and JPEG fallbacks.
Serving the right format
The <picture> element lets you serve different formats to different browsers:
<picture>
<source srcset="image.avif" type="image/avif">
<source srcset="image.webp" type="image/webp">
<img src="image.jpg" alt="Description" width="800" height="600">
</picture>
The browser picks the first source it supports. AVIF-capable browsers get the smallest file. Others fall back to WebP, then JPEG.
For automated pipelines, check the Accept header on the server side. Browsers that support WebP send image/webp in the Accept header. CDNs like Cloudflare and Fastly can do format negotiation automatically.
Responsive images
Compression is only half the battle. Serving a 2400px image to a 400px-wide mobile viewport wastes bandwidth regardless of format.
<img
srcset="image-400.webp 400w, image-800.webp 800w, image-1200.webp 1200w"
sizes="(max-width: 600px) 400px, (max-width: 1000px) 800px, 1200px"
src="image-800.webp"
alt="Description"
>
The browser selects the appropriate size based on the viewport width and device pixel ratio. Combined with modern format compression, this can reduce image payload by 80-90% compared to a single unoptimized JPEG.
Common mistakes
Not specifying width and height. Without explicit dimensions, the browser cannot allocate space for the image before it loads, causing layout shifts. Always set width and height attributes.
Compressing already-compressed images. Re-compressing a JPEG introduces generation loss. Each compression cycle introduces more artifacts. Always compress from the original source file.
Using PNG for photographs. A 3000x2000 photo as PNG can be 15MB. As JPEG at quality 80, it is under 500KB. There is no visual justification for PNG on photographs.
Skipping lazy loading. Images below the fold do not need to load immediately. Add loading="lazy" to defer loading until the image approaches the viewport.
For quick one-off compression without setting up a build pipeline -- compressing assets for a static site, optimizing images for a blog post, or testing different quality levels -- I use an image compressor at zovo.one/free-tools/image-compressor that supports multiple formats and shows the size reduction in real time.
Compress your images. Use modern formats. Serve responsive sizes. These three steps alone can cut your page weight in half.
I'm Michael Lip. I build free developer tools at zovo.one. 350+ tools, all private, all free.
Top comments (0)