Images are usually the largest part of a page by bytes, and the hero image is often the element that decides your Largest Contentful Paint. Fixing them is rarely glamorous, but it is dependable. This guide covers which format to use, how to size images for the screen they appear on, how srcset and lazy loading work, and the exact commands to convert a folder of files.
Start by measuring
Do not optimize blindly. Run the page weight analyzer to see how many bytes images contribute and which files are biggest. Then open your browser's Network tab, filter by Img and sort by size. The top five files are normally where most of the gain is. If images are a small share of the page, your time is better spent elsewhere; see the slow site diagnosis flowchart.
Pick the right format
| Format | Best for | Notes |
|---|---|---|
| JPEG | Photographs, universal fallback | Lossy, no transparency |
| PNG | Screenshots, flat graphics needing exact pixels or transparency | Lossless, large for photos |
| WebP | Photos and graphics, lossy or lossless, with transparency | Supported by all current major browsers |
| AVIF | Photos where you want the smallest files | Often smaller than WebP at similar quality, but slower to encode; supported by current major browsers |
| SVG | Logos, icons, simple illustrations | Vector, scales to any size, compresses well with gzip or Brotli |
| GIF | Avoid | Use a short muted looping video instead for animation |
How much smaller WebP or AVIF will be depends on the image content and the quality setting you choose, so measure on your own files rather than trusting a headline percentage. The practical rule: serve AVIF or WebP to browsers that support them, with JPEG or PNG as a fallback inside a picture element.
Size images for where they are shown
Format is only half the problem. A 4000-pixel-wide photo shown in a 600-pixel column wastes most of its bytes no matter how well it is compressed.
- Find the largest size at which the image is ever displayed, in CSS pixels.
- Multiply by 2 for high-density screens. A slot that is 600 CSS pixels wide needs about 1200 pixels of source width for a 2x display. Going beyond 2x rarely adds visible quality.
- Resize the source to that width and no larger.
- Strip metadata you do not need (EXIF, GPS, color profiles that are not required).
Also set explicit width and height attributes on every img. The browser uses them to reserve space before the file loads, which prevents layout shift and protects your Cumulative Layout Shift score. See the Core Web Vitals guide.
Responsive images with srcset and sizes
A single file cannot suit both a phone and a 4K monitor. srcset lets the browser pick from several widths.
<img
src="/img/hero-800.jpg"
srcset="/img/hero-480.jpg 480w,
/img/hero-800.jpg 800w,
/img/hero-1200.jpg 1200w,
/img/hero-1600.jpg 1600w"
sizes="(min-width: 1000px) 800px, 100vw"
width="1600" height="900"
alt="Describe what the image shows">
Here w descriptors tell the browser each file's real pixel width, and sizes tells it how wide the image will be displayed. Without sizes, the browser assumes the image fills the viewport and may download something larger than needed.
To combine modern formats with a fallback, wrap sources in picture:
<picture>
<source type="image/avif" srcset="/img/hero-800.avif 800w, /img/hero-1600.avif 1600w" sizes="(min-width: 1000px) 800px, 100vw">
<source type="image/webp" srcset="/img/hero-800.webp 800w, /img/hero-1600.webp 1600w" sizes="(min-width: 1000px) 800px, 100vw">
<img src="/img/hero-800.jpg" width="1600" height="900" alt="Describe what the image shows">
</picture>
The browser takes the first source whose type it understands, and falls back to the img otherwise.
Lazy loading
Add loading="lazy" to images that start below the visible area:
<img src="/img/gallery-3.webp" width="600" height="400" loading="lazy" decoding="async" alt="Gallery photo">
Two rules matter:
- Never lazy-load the main above-the-fold image. Delaying your LCP image makes the metric worse. Leave it with default loading and give it
fetchpriority="high". - Always include dimensions on lazy images, or the page will jump as they load.
<img src="/img/hero-800.webp" width="1600" height="900" fetchpriority="high" alt="Describe what the image shows">
Tooling and commands
cwebp (WebP)
cwebp is part of Google's libwebp tools, available in most package managers (for example the webp package on Debian and Ubuntu).
# Lossy, quality 80, resized to 1200 px wide (height auto)
cwebp -q 80 -resize 1200 0 input.jpg -o output.webp
# Lossless, for graphics with sharp edges
cwebp -lossless input.png -o output.webp
Batch convert a folder:
for f in *.jpg; do cwebp -q 80 "$f" -o "${f%.jpg}.webp"; done
avifenc (AVIF)
avifenc ships with libavif. Option names have changed between releases, so run avifenc --help for your version.
# Quality-based encoding (recent libavif versions)
avifenc -q 60 input.jpg output.avif
AVIF encoding is noticeably slower than WebP, so run it once at build time, not on every request.
sharp (Node.js)
sharp is a widely used Node library for build scripts and image pipelines.
const sharp = require('sharp');
for (const width of [480, 800, 1200, 1600]) {
await sharp('hero.jpg')
.resize({ width, withoutEnlargement: true })
.webp({ quality: 80 })
.toFile(`hero-${width}.webp`);
await sharp('hero.jpg')
.resize({ width, withoutEnlargement: true })
.avif({ quality: 50 })
.toFile(`hero-${width}.avif`);
}
ImageMagick
magick input.jpg -resize 1200x -strip -quality 82 output.jpg
On older installs the command is convert instead of magick.
Quality settings
Start around 75 to 85 for WebP and JPEG, and lower for AVIF (its quality scale is not equivalent), then compare visually at full size. Pick the lowest setting where you cannot see a difference on the images that matter most. Photos with fine texture need more quality than flat illustrations.
Let a CDN do it, with caution
Many CDNs and hosting platforms can resize and convert images on the fly and serve WebP or AVIF depending on the browser's Accept header. Check which one yours offers with the CDN detector and your provider's current docs. If you use content negotiation, the server must send Vary: Accept so caches do not hand an AVIF file to a browser that cannot read it. Inspect response headers with the HTTP header checker.
Cache them for a long time
Images rarely change. Give versioned or fingerprinted file names a long cache lifetime:
Cache-Control: public, max-age=31536000, immutable
If you cannot rename files when they change, use a shorter lifetime and rely on revalidation. See Cloudflare cache rules and purge if you cache at the edge.
A short checklist
- Measure with the page weight analyzer; list the largest images.
- Resize to at most 2x the displayed width.
- Convert photos to AVIF or WebP, keeping a JPEG fallback.
- Use
srcsetandsizes; setwidthandheight. - Lazy-load below-the-fold images only; prioritize the LCP image.
- Cache with long lifetimes and verify headers.
- Re-run the speed test and compare.
Next steps
- Scan your page with the page weight analyzer.
- Re-test with the website speed test after changes.
- Check LCP and layout shift in the Core Web Vitals tool.
- Read Shopify store speed optimization or the WordPress slow site checklist for platform-specific steps.
FAQ
Should I replace all my JPEGs with AVIF?
Not blindly. Convert the largest and most visible images first, keep a fallback, and compare quality visually. AVIF encodes slowly, so do it at build time.
Does lazy loading hurt SEO?
Native loading="lazy" is understood by modern search crawlers. Avoid lazy-loading the main above-the-fold image, which can slow LCP.
What image dimensions should I upload?
About twice the largest CSS width the image is displayed at, no more. Provide several widths with srcset for responsive layouts.
Is WebP or AVIF better?
AVIF is often smaller at similar quality but encodes slower. WebP is faster to produce and very widely supported. Using both with a fallback is common.
Get notified when this guide updates
Plus occasional practical tips on uptime and monitoring, nothing else. Unsubscribe anytime.
This page is general information written by our editorial team and updated when documentation changes. Servers and providers differ, so test changes safely first. See our editorial policy and disclaimer.