Tutorial

Image Optimization for Web: WebP, AVIF, Lazy Loading, and CDN

June 05, 2026

Back to Blog
Managing servers the hard way? Panelica gives you isolated hosting, built-in Docker and AI-assisted management.
Start free

Images Are the Heaviest Part of the Web

According to the HTTP Archive, images account for roughly 50% of total page weight on the average website. A single high-resolution hero image can easily be 2-5 MB, which at typical mobile connection speeds means seconds of loading time. When Google measures your Largest Contentful Paint (LCP), that hero image is often the LCP element — and if it takes 4 seconds to download, your Core Web Vitals score is effectively torpedoed.

The good news is that image optimization is one of the highest-impact, lowest-effort performance improvements you can make. By converting to modern formats like WebP and AVIF, implementing lazy loading for off-screen images, serving responsive images with srcset, and leveraging CDN delivery, you can reduce image payload by 60-80% without any visible quality loss.

This guide covers every aspect of image optimization — from format selection and conversion tools to server-side configuration and automated workflows.

Image Format Comparison: JPEG, PNG, WebP, and AVIF

Choosing the right format is the single most impactful decision in image optimization. Each format has distinct strengths, and understanding when to use each one can dramatically reduce file sizes.

FormatBest ForCompressionTransparencyAnimationBrowser Support
JPEGPhotos, complex imagesLossyNoNo100%
PNGGraphics, screenshots, transparencyLosslessYesNo100%
WebPEverything (photos + graphics)BothYesYes97%+
AVIFPhotos (highest compression)BothYesYes93%+
SVGIcons, logos, illustrationsVector (lossless)YesYes100%

File Size Comparison: Same Image, Different Formats

To illustrate the real-world impact of format choice, here are actual file sizes for a 1920x1080 photograph at comparable visual quality:

420 KB
JPEG (quality 85)
285 KB
WebP (quality 80)
195 KB
AVIF (quality 60)
54%
AVIF savings vs JPEG
Quality Settings Are Not Equivalent Across Formats. JPEG quality 85, WebP quality 80, and AVIF quality 60 all produce roughly similar visual results. Each codec has its own quality scale, so do not blindly use the same number across formats. Always compare output visually at your chosen quality level.

Converting Images: CLI Tools and Batch Processing

Let us look at the most practical tools for converting images to modern formats. These are all server-side tools that can be integrated into build scripts, CI/CD pipelines, or cron jobs.

Converting to WebP

# Install cwebp (part of libwebp)
$ apt install webp

# Convert single image
$ cwebp -q 80 input.jpg -o output.webp
Saving file 'output.webp'
File: input.jpg
Dimension: 1920 x 1080
Output: 285432 bytes (1.11 bpp)

# Batch convert all JPEGs in a directory
$ for f in *.jpg; do
  cwebp -q 80 "$f" -o "${f%.jpg}.webp"
done

# Convert PNGs (preserving transparency)
$ cwebp -q 80 -alpha_q 90 logo.png -o logo.webp

Converting to AVIF

# Install avifenc (part of libavif-tools)
$ apt install libavif-tools

# Convert single image (speed 6 = balanced)
$ avifenc --min 20 --max 35 --speed 6 input.jpg output.avif
Successfully encoded: output.avif
Encode time: 2.34 seconds

# Batch convert with parallel processing
$ find . -name "*.jpg" -print0 | xargs -0 -P4 -I{} \
  sh -c 'avifenc --min 20 --max 35 --speed 6 "$1" "${1%.jpg}.avif"' _ {}
AVIF Encoding is Slow. AVIF compression provides the best file sizes but is significantly slower to encode than WebP. A 1920x1080 image might take 2-5 seconds with AVIF versus milliseconds with WebP. For real-time conversion, use WebP. For pre-processed static assets, AVIF is worth the encoding time.

Using ImageMagick for Bulk Operations

# Resize + convert + optimize in one command
$ magick input.jpg -resize 1200x -strip -quality 80 output.webp

# Create responsive image variants
$ for size in 400 800 1200 1600; do
  magick input.jpg -resize ${size}x -strip \
    -quality 80 "output-${size}w.webp"
done
Created: output-400w.webp (28KB)
Created: output-800w.webp (85KB)
Created: output-1200w.webp (168KB)
Created: output-1600w.webp (245KB)

Sharp (Node.js) for Build Pipelines

// Sharp is the fastest Node.js image processor
const sharp = require('sharp');

// Convert to WebP with resize
await sharp('input.jpg')
  .resize(1200, null, { withoutEnlargement: true })
  .webp({ quality: 80, effort: 4 })
  .toFile('output.webp');

// Convert to AVIF
await sharp('input.jpg')
  .resize(1200)
  .avif({ quality: 50, effort: 5 })
  .toFile('output.avif');

// Generate multiple sizes for srcset
const sizes = [400, 800, 1200, 1600];
for (const width of sizes) {
  await sharp('input.jpg')
    .resize(width)
    .webp({ quality: 80 })
    .toFile(`output-${width}w.webp`);
}

Nginx Auto-Conversion with try_files

One of the most elegant server-side optimization strategies is to serve WebP or AVIF images automatically when the browser supports them, without changing any HTML. This is done by checking the Accept header and using Nginx's try_files directive.

# In the http block — map Accept header to file suffix
map $http_accept $webp_suffix {
  default "";
  "~*webp" ".webp";
}

map $http_accept $avif_suffix {
  default "";
  "~*avif" ".avif";
}

# In the server block — try AVIF first, then WebP, then original
location ~* \.(jpg|jpeg|png|gif)$ {
  add_header Vary Accept;
  try_files $uri$avif_suffix $uri$webp_suffix $uri =404;
  expires 6M;
  add_header Cache-Control "public";
}
How It Works: When a browser requests /images/photo.jpg and sends Accept: image/avif,image/webp,*/*, Nginx first checks if /images/photo.jpg.avif exists on disk. If yes, it serves that. If not, it checks for /images/photo.jpg.webp. If neither exists, it serves the original JPEG. The Vary: Accept header ensures CDNs and proxies cache separate versions for each supported format.

The HTML Picture Element for Format Fallback

While server-side content negotiation works transparently, the HTML <picture> element gives you explicit control over format selection in your markup. This approach is more reliable across all CDN configurations and provides a clear fallback chain.

<!-- Progressive format fallback -->
<picture>
  <!-- Best compression — AVIF -->
  <source srcset="hero.avif" type="image/avif">

  <!-- Good compression — WebP -->
  <source srcset="hero.webp" type="image/webp">

  <!-- Universal fallback — JPEG -->
  <img src="hero.jpg" alt="Hero image"
    width="1200" height="630"
    loading="eager"
    fetchpriority="high">
</picture>

Lazy Loading: Only Load What Users See

Lazy loading defers the loading of off-screen images until the user scrolls near them. This reduces initial page load time, saves bandwidth for users who do not scroll to the bottom, and improves Core Web Vitals by prioritizing above-the-fold content.

Native Browser Lazy Loading

The simplest approach is the loading="lazy" attribute, supported by all modern browsers:

<!-- Above the fold: NEVER lazy load -->
<img src="hero.jpg" alt="Hero"
  width="1200" height="630"
  loading="eager"
  fetchpriority="high">

<!-- Below the fold: always lazy load -->
<img src="product-1.jpg" alt="Product"
  width="400" height="300"
  loading="lazy">

<img src="product-2.jpg" alt="Product"
  width="400" height="300"
  loading="lazy">
Never Lazy Load the LCP Image! The largest visible element on your page (usually the hero image) should always load eagerly with loading="eager" and fetchpriority="high". Lazy loading the LCP element adds a significant delay because the browser only discovers it needs to load the image after layout calculation, resulting in terrible LCP scores.

Intersection Observer for Advanced Lazy Loading

// Advanced lazy loading with Intersection Observer
const observer = new IntersectionObserver((entries) => {
  entries.forEach(entry => {
    if (entry.isIntersecting) {
      const img = entry.target;
      img.src = img.dataset.src;
      if (img.dataset.srcset) {
        img.srcset = img.dataset.srcset;
      }
      img.classList.add('loaded');
      observer.unobserve(img);
    }
  });
}, {
  rootMargin: '200px' // Start loading 200px before visible
});

document.querySelectorAll('img[data-src]').forEach(img => {
  observer.observe(img);
});

Responsive Images with srcset and sizes

Responsive images ensure that mobile users download a small image while desktop users get a full-resolution version. Without srcset, a mobile user on a 360px-wide screen downloads the same 1920px image as a desktop user — wasting 80% of the bandwidth.

<!-- Responsive image with format fallback -->
<picture>
  <source
    type="image/avif"
    srcset="photo-400w.avif 400w,
           photo-800w.avif 800w,
           photo-1200w.avif 1200w,
           photo-1600w.avif 1600w"
    sizes="(max-width: 768px) 100vw, 50vw">

  <source
    type="image/webp"
    srcset="photo-400w.webp 400w,
           photo-800w.webp 800w,
           photo-1200w.webp 1200w,
           photo-1600w.webp 1600w"
    sizes="(max-width: 768px) 100vw, 50vw">

  <img src="photo-1200w.jpg"
    srcset="photo-400w.jpg 400w, photo-800w.jpg 800w,
           photo-1200w.jpg 1200w, photo-1600w.jpg 1600w"
    sizes="(max-width: 768px) 100vw, 50vw"
    alt="Product photo" width="1200" height="800"
    loading="lazy">
</picture>

Image Compression Tools

Beyond format conversion, you can further optimize images by removing metadata, optimizing color profiles, and fine-tuning compression parameters. Here are the best CLI tools for each format:

ToolFormatTypeTypical Savings
jpegoptimJPEGLossless + lossy10-30%
jpegtranJPEGLossless only5-15%
optipngPNGLossless10-25%
pngquantPNGLossy (quantization)40-70%
svgoSVGLossless (minify)20-50%
gifsicleGIFLossy + lossless15-40%
# Lossless JPEG optimization (strip metadata)
$ jpegoptim --strip-all --all-progressive photo.jpg
photo.jpg 420832 --> 378240 bytes (10.12%), optimized.

# Lossy PNG quantization (massive savings)
$ pngquant --quality=65-80 --strip --force screenshot.png
Saved: screenshot-fs8.png (148KB from 520KB)

# SVG minification
$ npx svgo icon.svg -o icon.min.svg
icon.svg: 12.4 KiB -> 4.8 KiB (61.3% reduction)

# Complete optimization pipeline
$ find /var/www/uploads -name "*.jpg" -exec jpegoptim --strip-all {} \;
$ find /var/www/uploads -name "*.png" -exec pngquant --force --ext .png {} \;

WordPress Image Optimization

WordPress is the most common CMS, and it has specific image handling behaviors you need to understand for effective optimization.

WordPress Default Behavior

Since WordPress 5.8, the platform generates WebP images automatically when your server's PHP has WebP support (via GD or Imagick). WordPress also generates multiple sizes (thumbnail, medium, large, full) for each uploaded image. Since WP 6.1, it adds loading="lazy" to all images except the first one on the page.

Server-Side PHP Requirements

Ensure your PHP installation includes either GD or Imagick with WebP support. Check with php -r "var_dump(gd_info());" and look for "WebP Support => enabled". For AVIF support, you need PHP 8.1+ with GD compiled with AVIF libraries or Imagick 7.0.25+.

# Check PHP GD WebP/AVIF support
$ php -r "print_r(gd_info());" | grep -i "webp\|avif"
[WebP Support] => 1
[AVIF Support] => 1

# Bulk convert existing WordPress uploads to WebP
$ find /home/user/public_html/wp-content/uploads \
  -name "*.jpg" -o -name "*.png" | while read f; do
  cwebp -q 80 "$f" -o "${f}.webp" 2>/dev/null
done
Converted 2,847 images to WebP format

Image CDN Services

For the ultimate in image optimization automation, image CDN services transform and deliver images on-the-fly. They handle format conversion, resizing, compression, and edge caching in a single service.

Cloudflare Polish and Image Resizing

If you are using Cloudflare, Polish automatically compresses images passing through their CDN. In "lossy" mode, it also converts JPEG and PNG to WebP when the browser supports it. Cloudflare Image Resizing (available on Pro+ plans) provides URL-based transformation — you can request any size or format by modifying the URL parameters.

# Cloudflare Image Resizing URL format
https://example.com/cdn-cgi/image/width=800,quality=80,format=auto/images/photo.jpg

# In HTML
<img src="/cdn-cgi/image/width=400,format=auto/images/photo.jpg"
  srcset="/cdn-cgi/image/width=400,format=auto/images/photo.jpg 400w,
         /cdn-cgi/image/width=800,format=auto/images/photo.jpg 800w,
         /cdn-cgi/image/width=1200,format=auto/images/photo.jpg 1200w"
  sizes="(max-width: 768px) 100vw, 50vw"
  alt="Product photo">

Monitoring Image Performance

After implementing image optimizations, you need to monitor their effectiveness. Here is what to track:

  • Total image weight per page (target: under 500KB for mobile)
  • WebP/AVIF adoption rate (what percentage of image requests use modern formats)
  • LCP element load time (the hero image should load in under 1.5 seconds)
  • Cache hit ratio for images (should be above 95% at the CDN)
  • Lighthouse image audit score (no "properly size images" or "serve in next-gen formats" warnings)
# Check which image formats your site is actually serving
$ curl -sI -H "Accept: image/avif,image/webp,*/*" \
  https://example.com/images/hero.jpg | grep -i content-type
Content-Type: image/avif

# Without modern format support
$ curl -sI -H "Accept: image/jpeg" \
  https://example.com/images/hero.jpg | grep -i content-type
Content-Type: image/jpeg
Panelica and Image Optimization: Panelica's Nginx configuration supports WebP/AVIF content negotiation. Combined with Cloudflare integration, your images can be automatically optimized and cached at the edge. The Nginx try_files setup for format fallback is included in Panelica's domain configuration templates, so every new site benefits from modern image format delivery without additional setup.

Implementation Priority

If you are starting from scratch, here is the order in which to implement image optimizations for maximum impact per effort:

1
Compress existing images with jpegoptim/pngquant — no format change, immediate savings, zero risk of compatibility issues.
2
Add lazy loading to all below-the-fold images — single attribute change per image, dramatic reduction in initial page weight.
3
Convert to WebP — 97%+ browser support means almost universal compatibility, with 25-35% file size reduction over JPEG.
4
Implement responsive images with srcset — prevents mobile users from downloading desktop-sized images.
5
Add AVIF as first choice in your picture element — 50%+ savings over JPEG for browsers that support it, with WebP as fallback.
6
Enable CDN image optimization — automates everything above and adds edge caching for global performance.

Image optimization is not glamorous work, but it delivers some of the most measurable performance improvements possible. A site that takes the time to properly optimize images — using modern formats, lazy loading, responsive delivery, and CDN caching — will consistently outperform competitors on both page speed metrics and search engine rankings. Start with the low-hanging fruit of compression and lazy loading, then progressively adopt WebP, AVIF, and srcset for the full performance advantage.

Security-first hosting panel

Run your servers on a modern panel.

Panelica is a modern, security-first hosting panel — isolated services, built-in Docker and AI-assisted management, with one-click migration from any panel.

Zero-downtime migration Fully isolated services Cancel anytime
Share:
Skip the next emergency patch.