LESSON 3 โฑ๏ธ 13 min read

Image Optimization

Why Images Matter

Images typically account for 50-75% of page weight. Optimizing them has the biggest performance impact.

Modern Image Formats

FormatBest ForBrowser Support
WebPPhotos, graphics97%+
AVIFBest compression85%+
SVGIcons, logos100%
PNGTransparency100%
JPEGPhotos (fallback)100%

WebP Conversion

// Generate WebP on upload
add_filter('wp_generate_attachment_metadata', function($metadata, $attachment_id) {
    $file = get_attached_file($attachment_id);
    
    if (wp_image_editor_supports(['mime_type' => 'image/webp'])) {
        $editor = wp_get_image_editor($file);
        if (!is_wp_error($editor)) {
            $webp_path = preg_replace('/.(jpg|jpeg|png)$/i', '.webp', $file);
            $editor->save($webp_path, 'image/webp');
        }
    }
    
    return $metadata;
}, 10, 2);

Serving WebP with Fallback

<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>

Lazy Loading

Defer loading images until they're needed:

<!-- Native lazy loading (WordPress 5.5+) -->
<img src="image.jpg" loading="lazy" alt="">

<!-- Disable for above-fold images -->
<img src="hero.jpg" loading="eager" fetchpriority="high" alt="">

WordPress Lazy Loading

// WordPress adds loading="lazy" by default
// Disable for specific images
add_filter('wp_img_tag_add_loading_attr', function($value, $image, $context) {
    // Don't lazy load featured images
    if ($context === 'the_content' && strpos($image, 'wp-post-image') !== false) {
        return false;
    }
    return $value;
}, 10, 3);

// Or use fetchpriority for LCP images
add_filter('wp_get_attachment_image_attributes', function($attr, $attachment) {
    if (is_singular() && has_post_thumbnail() && get_post_thumbnail_id() == $attachment->ID) {
        $attr['fetchpriority'] = 'high';
        $attr['loading'] = 'eager';
    }
    return $attr;
}, 10, 2);

Responsive Images

Serve appropriate sizes for each device:

<!-- Responsive with srcset -->
<img 
    src="image-800.jpg"
    srcset="image-400.jpg 400w,
            image-800.jpg 800w,
            image-1200.jpg 1200w,
            image-1600.jpg 1600w"
    sizes="(max-width: 600px) 100vw,
           (max-width: 1200px) 50vw,
           800px"
    alt="Description"
>

WordPress Responsive Images

WordPress generates srcset automatically. Customize sizes:

// Add custom image sizes
add_image_size('card-thumbnail', 400, 300, true);
add_image_size('hero-large', 1600, 900, true);

// Customize srcset sizes
add_filter('wp_calculate_image_sizes', function($sizes, $size, $image_src, $image_meta) {
    // For full-width images
    if ($size[0] >= 1200) {
        return '100vw';
    }
    return $sizes;
}, 10, 4);

Image Compression

Recommended Tools

ToolTypeCompression
ShortPixelPluginLossy/Lossless
ImagifyPluginLossy/Lossless
SmushPluginLossless
SquooshWeb appManual
ImageOptimMac appLossless

Command Line Compression

# WebP conversion with cwebp
cwebp -q 80 input.jpg -o output.webp

# Batch conversion
for file in *.jpg; do
    cwebp -q 80 "$file" -o "${file%.jpg}.webp"
done

# AVIF conversion
avifenc --speed 6 --quality 70 input.jpg output.avif

# Optimize existing JPEGs
jpegoptim --strip-all --max=85 *.jpg

# Optimize PNGs
optipng -o5 *.png

SVG Optimization

# Install SVGO
npm install -g svgo

# Optimize SVG
svgo input.svg -o output.svg

# Batch optimize
svgo -f ./icons/ -o ./icons-optimized/

Safe SVG Upload

// Allow SVG uploads (with sanitization)
add_filter('upload_mimes', function($mimes) {
    $mimes['svg'] = 'image/svg+xml';
    return $mimes;
});

// Sanitize SVG content
add_filter('wp_handle_upload_prefilter', function($file) {
    if ($file['type'] === 'image/svg+xml') {
        // Use a library like SVG Sanitizer
        $sanitizer = new enshrinedsvgSanitizeSanitizer();
        $content = file_get_contents($file['tmp_name']);
        $clean = $sanitizer->sanitize($content);
        file_put_contents($file['tmp_name'], $clean);
    }
    return $file;
});

Preventing Layout Shift

/* Aspect ratio boxes */
.image-container {
    aspect-ratio: 16 / 9;
    overflow: hidden;
}

.image-container img {
    width: 100%;
    height: 100%;
    object-fit: cover;
}

/* Progressive loading placeholder */
.image-placeholder {
    background: linear-gradient(135deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%);
    background-size: 200% 100%;
    animation: shimmer 1.5s infinite;
}

@keyframes shimmer {
    0% { background-position: 200% 0; }
    100% { background-position: -200% 0; }
}

CDN Image Optimization

Many CDNs offer automatic image optimization:

// Cloudflare Polish (automatic)
// BunnyCDN Image Processing
// Imgix/Cloudinary URLs

// Example: Dynamic resizing via CDN
function cdn_image_url($url, $width, $height = null) {
    $base = 'https://cdn.example.com/';
    $params = "w={$width}";
    if ($height) {
        $params .= "&h={$height}&fit=crop";
    }
    return "{$base}{$url}?{$params}&format=auto&quality=80";
}

Next Steps

In the next lesson, we'll optimize database queries and clean up bloat.

๐ŸŽฏ Lesson Complete! You can now implement comprehensive image optimization.