Image Optimization
Why Images Matter
Images typically account for 50-75% of page weight. Optimizing them has the biggest performance impact.
Modern Image Formats
| Format | Best For | Browser Support |
|---|---|---|
| WebP | Photos, graphics | 97%+ |
| AVIF | Best compression | 85%+ |
| SVG | Icons, logos | 100% |
| PNG | Transparency | 100% |
| JPEG | Photos (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
| Tool | Type | Compression |
|---|---|---|
| ShortPixel | Plugin | Lossy/Lossless |
| Imagify | Plugin | Lossy/Lossless |
| Smush | Plugin | Lossless |
| Squoosh | Web app | Manual |
| ImageOptim | Mac app | Lossless |
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 *.pngSVG 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.