Why image optimization matters more than ever in 2026
Images are still 60-70% of the bytes downloaded on a typical WordPress page. Despite improvements in JS bundlers and modern CSS, image bytes dominate.
Google's Core Web Vitals (especially LCP — Largest Contentful Paint) are heavily dependent on image performance. A 2MB hero image kills LCP. A 200KB optimized hero image gives you a fast LCP and ranking benefits.
This article is our complete 2026 image stack for WordPress sites.
The four layers of image optimization
Layer 1 — Format conversion
WebP and AVIF deliver 30-50% smaller files than JPEG/PNG at equivalent quality. Modern browsers all support WebP; AVIF support is now universal (Safari, Chrome, Firefox all support).
Layer 2 — Responsive serving
<img srcset="..."> and <picture> elements deliver appropriately-sized images to each device. WordPress core handles this automatically when you set up image sizes correctly.
Layer 3 — Lazy loading
Images below the fold should not load until the user scrolls near them. WordPress core supports loading="lazy" since 5.5.
Layer 4 — CDN delivery
Images served from edge POPs near visitors. Drops latency from 200ms to 20ms.
Layer 1 — Converting to WebP/AVIF
Option A: at upload (via plugin)
Plugins we deploy:
- Imagify — auto-converts on upload, keeps originals as backup. Free tier covers ~25MB/month
- EWWW Image Optimizer — local conversion, no API call needed
- ShortPixel Adaptive Images — converts AND serves via their CDN
- Smush Pro — WPMU's option, broader feature set
For sites under 1000 images, Imagify free tier is enough. For larger sites, ShortPixel or EWWW.
Option B: at CDN (no plugin needed)
Some CDNs do format conversion on-the-fly:
- BunnyNet + Bunny Optimizer — adds WebP/AVIF for image URLs based on Accept header
- Cloudflare Polish — Pro plan; converts on request
- Fastly Image Optimizer — premium add-on
CDN-level conversion is convenient because you don't store WebP/AVIF originals. The CDN handles the format negotiation. Slightly slower first-request (CDN converts and caches).
Option C: at build (for static or hybrid)
If you use Astro, Next.js, or a build process, image conversion happens once at build time. Most pure-WordPress sites don't have a build step.
Layer 2 — Responsive images
WordPress's wp_get_attachment_image() automatically generates srcset and sizes attributes. As long as your theme uses these functions (not raw <img> tags), responsive serving works.
For custom images via Advanced Custom Fields:
$image_id = get_field('hero_image');
echo wp_get_attachment_image(
$image_id,
'large', // size
false,
array(
'class' => 'hero-image',
'sizes' => '(max-width: 768px) 100vw, 1200px',
)
);For background images in CSS, you can't use srcset directly. Use the image-set() CSS function:
.hero {
background-image: url('hero-1x.webp');
background-image: image-set(
url('hero-1x.webp') 1x,
url('hero-2x.webp') 2x
);
}Image sizes — choose intentionally
WordPress generates many image sizes by default plus more from your theme. Each size is a copy of every uploaded image. Storage and processing cost.
In your theme functions.php:
// Remove default sizes you don't use
add_filter('intermediate_image_sizes', function($sizes) {
return array_diff($sizes, ['medium_large', '1536x1536', '2048x2048']);
});
// Add only sizes your theme uses
add_image_size('card-thumb', 400, 300, true); // hard crop
add_image_size('hero', 1600, 900, true);
add_image_size('hero-mobile', 800, 600, true);Run wp media regenerate after changing sizes to update existing images.
Layer 3 — Lazy loading correctly
WordPress 5.5+ adds loading="lazy" to most images automatically. But there are nuances.
Above-the-fold images should NOT lazy load
The hero image is what users see immediately. If you loading="lazy" it, LCP suffers. WordPress core tries to detect this and skips lazy on the first image, but it's not perfect.
Force eager loading for hero images:
echo wp_get_attachment_image(
$hero_id,
'hero',
false,
array('loading' => 'eager', 'fetchpriority' => 'high')
);fetchpriority="high" is a 2023+ attribute that tells the browser to prioritize this image download.
Below-the-fold images should always lazy load
Default WordPress behavior is correct here. Just verify your theme isn't disabling it.
Avoid lazy-loading background images
CSS background images can't be lazy-loaded natively. If you must lazy-load them, use Intersection Observer JS — but consider whether the image should be a real <img> instead.
Layer 4 — CDN delivery of images
Once optimized images exist, serve them from a CDN.
Cloudflare
If your domain is on Cloudflare, image URLs already go through Cloudflare. Enable:
- Polish (Pro plan) — auto WebP conversion
- Mirage (Pro plan) — adapts images for connection speed
- Auto Minify HTML — strips unnecessary attributes from
<img>tags
BunnyNet
Use the Bunny WordPress plugin to rewrite image URLs to your Bunny CDN URL:
Origin URL: https://yoursite.com/wp-content/uploads/2026/01/hero.jpg
CDN URL: https://yoursite.b-cdn.net/wp-content/uploads/2026/01/hero.jpgWith Bunny Optimizer ($9.50/month), Bunny converts to WebP/AVIF based on the Accept header.
Fastly
Fastly Image Optimizer takes parameters via URL query string:
https://yoursite.com/hero.jpg?width=800&height=600&format=webpThis is powerful for dynamic resizing but requires more code changes.
Lazy loading + CDN gotcha: 1x1 placeholder
Some lazy-load plugins (like a3 Lazy Load) replace the src with a 1x1 placeholder until the image scrolls into view. This works but causes a flash. Native loading="lazy" doesn't have this problem.
Use native loading="lazy" whenever possible. Avoid plugins that re-implement lazy loading badly.
The complete image stack we deploy
Our recommended setup for new WordPress sites in 2026:
- Upload plugin: Imagify (auto-converts to WebP, keeps originals)
- Theme: use
wp_get_attachment_image()everywhere, custom image sizes for each template - Above-fold images:
loading="eager" fetchpriority="high" - Below-fold: native
loading="lazy"(WordPress handles) - CDN: Cloudflare for blog/marketing sites, BunnyNet for image-heavy sites
- CDN image optimization: Cloudflare Polish (if Pro) or Bunny Optimizer
Measuring image performance
Before and after, run:
# Lighthouse CLI
npm install -g lighthouse
lighthouse https://yoursite.com/ --output html --output-path ./report.html
# Focus on:
# - Largest Contentful Paint (target: <2.5s)
# - Cumulative Layout Shift (target: <0.1)
# - Total bytes downloaded (image breakdown)For specific pages, use WebPageTest.org. It shows individual image sizes, formats, and load timing.
Common mistakes during image optimization
- Lazy loading the hero image — kills LCP
- Uploading 4000x3000 images for a 600px-wide blog — bytes-on-the-wire that no one sees
- Forgetting to regenerate sizes after
add_image_size()— new size never gets created for old uploads - CDN cache without versioning — image updates don't propagate; users see stale versions
When to call a specialist
Image optimization is a high-leverage performance lever. We routinely deliver 30-50% page-weight reduction in a 2-4 hour audit + implementation engagement.
Image optimization audit as part of our standard speed recovery. For broader performance work see speed recovery.

