Logo
WP Fix by Blimx

Image Optimization Stack for WordPress — WebP/AVIF, Lazy Load, CDN

Actualizado:
PerformanceFrontend

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

With 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=webp

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

  1. Upload plugin: Imagify (auto-converts to WebP, keeps originals)
  2. Theme: use wp_get_attachment_image() everywhere, custom image sizes for each template
  3. Above-fold images: loading="eager" fetchpriority="high"
  4. Below-fold: native loading="lazy" (WordPress handles)
  5. CDN: Cloudflare for blog/marketing sites, BunnyNet for image-heavy sites
  6. 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.