Web Image Optimization: Formats, Compression, and Performance
Images typically account for 50% or more of a page's total weight. That means image optimization is the single highest-leverage thing you can do for page speed on most sites — bigger impact than minifying JS, enabling HTTP/2, or any other common optimization. This guide covers how to choose formats, how compression actually works, how to serve the right size to every device, and how all of it connects to Core Web Vitals.
Picking the wrong format is the most common image optimization mistake. A PNG for a photo or a JPEG for a logo with transparency will always underperform, regardless of how aggressively you compress it.Format Selection: JPEG, PNG, WebP, AVIF, SVG
| Format | Best For | Transparency | Compression | Browser Support |
|---|---|---|---|---|
| JPEG | Photos, complex images | No | Lossy | Universal |
| PNG | Graphics, logos, screenshots with text | Yes (alpha) | Lossless | Universal |
| WebP | Everything (modern replacement for JPEG and PNG) | Yes (alpha) | Both | 97%+ |
| AVIF | Maximum compression, modern browsers | Yes (alpha) | Both | ~90% |
| SVG | Icons, logos, illustrations, charts | Yes | Vector (scalable) | Universal |
| GIF | Simple animations (use WebP/video instead) | 1-bit only | Lossless | Universal |
JPEG
JPEG is still the right choice for photographs and photographic-quality images when you need maximum compatibility. It uses DCT-based lossy compression that's very efficient for continuous-tone images — the kind where adjacent pixels are usually similar in color. It handles noise and gradients well.
JPEG cannot do transparency. If your photo needs a transparent background, use WebP or PNG instead. JPEG also accumulates quality loss every time you re-save — always keep your original and export fresh from it rather than re-saving the compressed version.
PNG
PNG uses lossless compression, which makes it ideal for images with sharp edges, flat colors, or text. Screenshots look better as PNG than JPEG because JPEG's block-based compression creates visible artifacts around sharp transitions. PNG supports full alpha transparency (not just 1-bit like GIF), making it the go-to for UI elements and logos.
The problem with PNG for photos: a full-quality photograph as a PNG is enormous — often 10x larger than a quality-80 JPEG. Never use PNG for photos unless you specifically need lossless quality.
WebP
WebP was designed by Google to replace both JPEG and PNG. It delivers 25-35% better compression than JPEG at equivalent visual quality, and 20-26% better than PNG for lossless images. It supports alpha transparency, both lossy and lossless modes, and animated images.
Browser support is now at 97%+ — Safari added support in version 14 (2020). For most projects, you can use WebP as the primary format with a JPEG/PNG fallback for the remaining 3%, or just drop the fallback entirely if your analytics show your audience is current.
AVIF
AVIF is the newest mainstream format, derived from the AV1 video codec. It achieves 50% smaller files than WebP at similar quality — a meaningful improvement. It also handles HDR content and wide color gamuts better than older formats.
The tradeoffs: encoding is slow (10-100x slower than WebP), tooling support is still maturing, and browser support is around 90% (Chrome 85+, Firefox 93+, Safari 16.4+). The right approach is to offer AVIF with WebP and JPEG fallbacks using the <picture> element.
SVG
For anything that's essentially a drawing — logos, icons, diagrams, charts, illustrations — SVG is almost always the right choice. It scales to any resolution without quality loss and is typically smaller than a raster equivalent once gzipped. You can style SVG with CSS, animate it, and make it interactive.
SVG is not suitable for photographs or complex photorealistic images. Don't use SVG for images that started as raster — exporting a photo to SVG will embed the raster data inside an SVG wrapper, giving you the worst of both worlds.
Decision Tree
Is it a line drawing, logo, or icon?
└── YES → SVG
Is it a photograph or photo-realistic image?
└── YES → WebP (lossy, quality 75-85)
+ JPEG fallback if broad compat needed
Does it need transparency?
└── YES → WebP (with alpha)
+ PNG fallback
Is it a simple animation?
└── YES → WebP (animated) or MP4 video
(avoid GIF — poor compression)
Otherwise:
└── WebP lossy → JPEG fallback
Convert images with the Image Converter tools — JPG to WebP, PNG to WebP, and more, all processed locally in your browser.
Lossy vs Lossless Compression
Permanently discards image data. The original cannot be recovered from the compressed version.
- Typical reduction: 60-90%
- Quality loss may be imperceptible or visible depending on settings
- Formats: JPEG, WebP (lossy mode), AVIF (lossy mode)
- Best for: photographs, complex scenes
Reduces file size without changing any pixel values. Decompresses to the exact original.
- Typical reduction: 10-50%
- No quality loss whatsoever
- Formats: PNG, WebP (lossless mode), AVIF (lossless mode), FLAC
- Best for: graphics with text, sharp edges, screenshots
The practical rule: use lossy for photos, lossless for everything that isn't a photo. An artifact in a photograph at quality 80 is usually invisible. The same artifact on a screenshot with text or a UI mockup looks terrible.
A common mistake is using PNG for photos out of a misguided preference for "quality." PNG lossless compression on a photo produces a file 3-5x larger than a quality-85 JPEG with no perceptible visual difference. You're paying enormous storage and bandwidth costs for no benefit.
Compression Settings and Quality
Quality settings are not standardized across tools — quality 80 in ImageMagick is different from quality 80 in Photoshop. Always do a visual comparison at your target quality setting. The numbers below are starting points, not universal truths.
JPEG Quality Guide
Quality 85-95: Hero images, portfolio, product photos
Minimal visible artifacts, large files
Use when image quality is the primary concern
Quality 75-85: Standard website images, blog photos
Good balance — most people can't spot artifacts
Recommended default for general use
Quality 60-75: Thumbnails, background images, decorative photos
Some artifacts visible at 100% zoom
Acceptable for small or non-focal images
Quality 40-60: Very low priority images, placeholders
Obvious quality loss — use sparingly
WebP Quality Guide
WebP quality 80 ≈ JPEG quality 85 (roughly equivalent perceptual quality)
Quality 80-90: High quality, ~30% smaller than equivalent JPEG
Quality 70-80: Good quality, recommended default
Quality 50-70: Thumbnails and lower-priority images
PNG Optimization
PNG doesn't have a quality slider since it's lossless. Optimization focuses on compression level and color depth:
- Compression level 6-9: Better compression, slower to encode. Use level 9 for files that will be served millions of times.
- Reduce color depth: If your image uses fewer than 256 colors, convert to PNG-8 (indexed color). Saves 30-60% vs PNG-32 with no quality loss for simple graphics.
- Strip metadata: EXIF data in PNG can add kilobytes of dead weight.
- Use pngquant for lossy PNG: pngquant reduces PNG-32 to PNG-8 with a perceptual color matching algorithm. Often achieves 60-80% reduction with near-identical appearance for icons and UI elements.
Real Size Comparison
Here's what a typical 1200×800 photo looks like at different formats and settings:
| Format / Setting | File Size | Notes |
|---|---|---|
| PNG (lossless) | ~2.1 MB | No quality loss, but huge for photos |
| JPEG quality 95 | ~650 KB | Near-lossless, large |
| JPEG quality 80 | ~180 KB | Good quality, typical for web |
| WebP quality 80 | ~120 KB | 35% smaller than equivalent JPEG |
| AVIF quality 60 | ~70 KB | Comparable quality to WebP 80 |
The jump from a raw PNG to a well-compressed WebP is a 17x file size reduction. On a page with 10 images, that's the difference between 21 MB and 1.2 MB.
The point of responsive images is simple: don't send a 1600px-wide image to a phone with a 375px display. The phone would download 10x more data than it can use, and it would actually look worse due to downscaling quirks.Responsive Images with srcset
srcset with Width Descriptors
<img
src="photo-800.jpg"
srcset="
photo-400.jpg 400w,
photo-800.jpg 800w,
photo-1200.jpg 1200w,
photo-1600.jpg 1600w"
sizes="(max-width: 600px) 100vw,
(max-width: 1200px) 50vw,
800px"
alt="Description"
width="800"
height="533"
>The sizes attribute tells the browser how wide the image will actually be rendered. The browser uses this, combined with the viewport width and device pixel ratio, to pick the best source from srcset. You don't control which one it picks — the browser decides based on current conditions, including network speed in some implementations.
Format Fallbacks with <picture>
<picture>
<source
type="image/avif"
srcset="photo-800.avif 800w, photo-1600.avif 1600w"
sizes="(max-width: 800px) 100vw, 800px"
>
<source
type="image/webp"
srcset="photo-800.webp 800w, photo-1600.webp 1600w"
sizes="(max-width: 800px) 100vw, 800px"
>
<img
src="photo-800.jpg"
srcset="photo-800.jpg 800w, photo-1600.jpg 1600w"
sizes="(max-width: 800px) 100vw, 800px"
alt="Description"
width="800"
height="533"
>
</picture>The browser reads <source> elements top to bottom and uses the first one it supports. AVIF-capable browsers get AVIF, WebP-capable browsers get WebP, everything else gets JPEG.
Retina / HiDPI Displays
Retina displays have a device pixel ratio (DPR) of 2 or more — they need twice as many physical pixels to display a crisp image. If you show a 400px image on a 2x display using a 400px source, it looks blurry.
<!-- Pixel density descriptors -->
<img
src="photo-400.jpg"
srcset="photo-400.jpg 1x, photo-800.jpg 2x, photo-1200.jpg 3x"
alt="Description"
width="400"
height="267"
>The good news: because you're serving WebP with good compression, serving 2x images is affordable. A WebP photo at 800px and quality 80 is often smaller than its JPEG equivalent at 400px.
Breakpoints to Cover
- 320-375px: Small phones (need 320-750px image at 1x-2x)
- 768px: Tablets (need 768-1536px image)
- 1024-1440px: Laptops and desktops
- 1920px+: Large monitors, 4K displays
You don't need a source for every possible width. Usually 3-4 sizes cover the range well — browsers interpolate and pick the nearest one up.
Native lazy loading is now supported in all major browsers and costs nothing to implement:Lazy Loading
<img
src="photo.jpg"
loading="lazy"
width="800"
height="600"
decoding="async"
alt="Description"
>The browser will not download this image until it's within a certain distance from the viewport. How far depends on connection speed and browser implementation — typically within one or two viewport heights.
Critical Rules for Lazy Loading
- Never lazy load above-the-fold images. If an image is visible when the page first loads, lazy loading it will delay its appearance and hurt your LCP score. Use
loading="eager"(the default) for hero images and anything visible on initial load. - Always set width and height. Without dimensions, the browser doesn't know how much space to reserve, and the page will reflow when the image loads (CLS penalty). Set the actual pixel dimensions of the image, not the CSS display size.
- Add
decoding="async". This tells the browser it can decode the image off the main thread, reducing jank.
<!-- Above the fold: load immediately, no lazy loading -->
<img
src="hero.webp"
alt="Hero image"
width="1200"
height="600"
fetchpriority="high"
><!-- Below the fold: lazy load --> <img src="gallery-1.webp" alt="Gallery image" width="400" height="300" loading="lazy" decoding="async" >
The fetchpriority="high" hint on the hero image tells the browser to prioritize loading it — useful when LCP is the hero image and you want the browser to fetch it as early as possible.
Google's Core Web Vitals directly measure user experience, and images affect all three main metrics.Core Web Vitals Impact
LCP — Largest Contentful Paint
LCP measures how long until the largest visible element is rendered. On most pages, this is a hero image. An unoptimized hero image delays LCP significantly.
- Compress the hero image aggressively (it's full-width so you can't see compression at normal zoom)
- Use WebP or AVIF — the format savings on a large image are substantial
- Don't lazy load the LCP image
- Preload the LCP image:
<link rel="preload" as="image" href="hero.webp"> - Serve it from a CDN geographically close to users
Good LCP: under 2.5 seconds. Most sites fail this because of large, unoptimized hero images.
CLS — Cumulative Layout Shift
CLS measures unexpected layout jumps. The most common cause is images without dimensions — the browser doesn't know how tall the image will be, so it reserves no space. When the image loads, content shifts down.
<!-- Bad: no dimensions, causes CLS -->
<img src="photo.jpg" alt="Photo">
<!-- Good: dimensions prevent layout shift -->
<img src="photo.jpg" alt="Photo" width="800" height="600">
<!-- For responsive CSS images, use aspect-ratio -->
<img
src="photo.jpg"
alt="Photo"
width="800"
height="600"
style="width: 100%; height: auto;"
>
FID/INP — Interaction Responsiveness
Image decoding can block the main thread and make pages unresponsive. Use decoding="async" to offload decoding, and avoid very large images that take a long time to decode even after downloading.
A CDN (Content Delivery Network) caches your images on servers geographically close to users. A user in Tokyo gets the image from a server in Tokyo, not from your origin server in Virginia. This reduces latency dramatically — often 100-500ms per image request.CDN Delivery
Image CDNs
Image CDNs go further: they transform images on-the-fly. You store one original, and the CDN serves the right format and size for each request.
// Cloudinary example: serve WebP at 800px wide, quality 80
https://res.cloudinary.com/yourcloud/image/upload/w_800,q_80,f_auto/photo.jpg// imgix example https://yoursite.imgix.net/photo.jpg?w=800&q=80&fm=webp
// Cloudflare Images example https://imagedelivery.net/hash/photo/w=800
f_auto (Cloudinary) or auto=format (imgix) instructs the CDN to detect what the browser supports and serve AVIF, WebP, or JPEG accordingly — without you having to maintain multiple copies or use <picture>.
Self-Hosted: Generate Multiple Formats at Build Time
If you don't use an image CDN, generate all format variants at build time:
# Using Sharp (Node.js)
const sharp = require('sharp');
await sharp('original.jpg')
.resize(800)
.webp({ quality: 80 })
.toFile('photo-800.webp');
await sharp('original.jpg')
.resize(800)
.avif({ quality: 60 })
.toFile('photo-800.avif');
await sharp('original.jpg')
.resize(800)
.jpeg({ quality: 80, mozjpeg: true })
.toFile('photo-800.jpg');
Tools and Workflows
Online Tools
For one-off conversions and quick optimization:
- Image Converter — Convert between formats including WebP, AVIF, JPEG, PNG
- Image Editor — Resize, crop, compress, and edit images in-browser
Command-Line Tools
# ImageMagick — versatile, handles almost any format
convert input.jpg -resize 1200x -quality 80 -strip output.jpg
-strip removes EXIF data
cwebp — Google's WebP encoder
cwebp -q 80 input.png -o output.webp
Sharp CLI — fast, Node.js based
npx sharp-cli input.jpg --resize 800 --format webp --quality 80 -o output.webp
SVGO — optimize SVG
svgo input.svg -o output.svg
svgo --multipass input.svg -o output.svg # More thorough
pngquant — lossy PNG compression
pngquant --quality=65-80 --output output.png input.png
Batch convert directory with ImageMagick
mogrify -format webp -quality 80 -path ./webp/ *.jpg
Build Tool Integration
- Vite:
vite-plugin-image-optimizer— compresses on build - Webpack:
image-minimizer-webpack-pluginwith Sharp or Squoosh - Next.js: Built-in
next/image— handles optimization, lazy loading, responsive images automatically - Astro: Built-in
<Image />component with similar automatic optimization - 11ty:
@11ty/eleventy-img— generates multiple sizes and formats at build time
Batch Optimization Workflow
For a typical project with a directory of source images:
1. Start with high-quality originals (keep these)
2. Determine display dimensions (max width at any breakpoint)
3. Generate 1x and 2x sizes for each breakpoint
4. Convert each to WebP (and optionally AVIF)
5. Keep JPEG fallbacks for old browsers
6. Strip EXIF metadata
7. Update HTML to use <picture> or srcsetBefore and After Examples
Product Photo (E-commerce)
| Version | File Size | What Changed |
|---|---|---|
| Camera original (RAW export) | 4.2 MB JPEG | — |
| Resized to 800×800px | 380 KB JPEG | Resized to display size |
| JPEG quality 80 | 160 KB JPEG | Compression applied |
| WebP quality 80 | 105 KB WebP | Format conversion |
| WebP + stripped EXIF | 98 KB WebP | Metadata removed |
Result: 4.2 MB down to 98 KB — a 97.7% reduction. The product photo looks identical at normal viewing size.
Blog Header Image
| Version | File Size | Notes |
|---|---|---|
| Original 2400px JPEG | 1.8 MB | Downloaded from stock photo site |
| Resized to 1200px | 290 KB JPEG | Max display width on desktop |
| WebP quality 75 | 85 KB WebP | Headers can tolerate lower quality |
Screenshot with Text
| Version | File Size | Notes |
|---|---|---|
| PNG screenshot (original) | 280 KB PNG | Lossless, text is sharp |
| WebP lossless | 190 KB WebP | Still lossless, text still sharp, 32% smaller |
| JPEG quality 80 | 65 KB JPEG | Text shows JPEG artifacts — not acceptable |
For screenshots, lossless compression is the right call even if it's larger. JPEG artifacts on text are clearly visible and unprofessional.
Optimization Checklist
Before Publishing Each Page
Image Tools