
Images are almost always the heaviest assets on a WordPress page, and they sit at the center of two Core Web Vitals metrics: Largest Contentful Paint (LCP) and Cumulative Layout Shift (CLS). Get the pipeline right and you can shave hundreds of milliseconds off your LCP and eliminate the layout jumps that hurt both user experience and your CLS score. Get it wrong and no amount of server tuning will rescue you.
This guide walks through every layer of that pipeline in the order that matters: format, dimensions, layout stability, loading priority, and finally delivery. Each step builds on the last.
Step 1: Serve Modern Formats (WebP and AVIF)
JPEG and PNG are still widely served, but they are not the right defaults anymore. WebP delivers meaningfully smaller files than JPEG at equivalent visual quality, and AVIF goes further still, though encoding is slower and browser support, while now broad, is slightly narrower than WebP.
The practical approach is to serve AVIF to browsers that request it, WebP as the fallback, and JPEG or PNG only for browsers that support neither. Modern browsers signal format support through the Accept request header, so a CDN or server-side plugin can make this decision automatically.
In WordPress, the most common way to handle this is through an optimization plugin that converts uploads on the fly or in bulk. When evaluating a plugin, confirm it does all of the following:
- Converts existing uploads as well as new ones (a bulk conversion tool is essential).
- Generates WebP and, ideally, AVIF variants alongside originals rather than replacing them.
- Rewrites image URLs or uses server-level rules so browsers receive the right format automatically, without touching your HTML.
- Preserves originals so you can regenerate if a better encoder appears later.
If you prefer a command-line approach, wp-cli paired with a conversion library gives you scripted bulk processing. A rough workflow looks like this:
# Regenerate all thumbnail sizes (after switching plugin or codec)
wp media regenerate --yesRun that after changing your optimization settings so every registered image size is rebuilt with the new format and quality settings.
Step 2: Serve Correctly Sized Images
Sending a 2400-pixel-wide image to a mobile screen that renders it at 400 CSS pixels wastes bandwidth even if the format is perfect. WordPress generates multiple sizes at upload time, and your theme should be using the srcset and sizes attributes on every <img> tag so the browser picks the appropriate variant for the viewport and device pixel ratio.
Check your theme is doing this by inspecting any image in DevTools. You should see a srcset listing several widths and a sizes attribute that describes how wide the image will actually appear. If either is missing, your theme or page builder is not wiring up responsive images correctly, and no CDN trick will compensate fully.
For custom image sizes defined in a theme or plugin, register them explicitly:
// In functions.php or a site plugin
add_image_size( 'hero-xl', 1920, 0, false );
add_image_size( 'hero-md', 960, 0, false );Then reference those sizes with wp_get_attachment_image() or get_the_post_thumbnail() and let WordPress build the srcset for you rather than hardcoding a single URL.
After adding new sizes, regenerate thumbnails so existing uploads get the new dimensions:
wp media regenerate --yesStep 3: Eliminate Layout Shift with Explicit Dimensions
CLS is capped at 0.1 for a passing score. One of the easiest ways to accumulate layout shift is to omit width and height attributes on images. Without them, the browser reserves no space while the image loads and reflowing content pushes everything down when it arrives.
Modern browsers use the width and height attributes to compute aspect ratio in CSS before the image loads, so setting them prevents the jump without locking the image into fixed pixel dimensions (as long as your CSS includes height: auto on images, which most themes do).
WordPress adds these attributes automatically when you insert images through the block editor or classic media library, but images inserted manually, through custom fields, or via page builders sometimes strip them. Audit with PageSpeed Insights, which flags images with missing dimensions directly in its diagnostics.
Step 4: Lazy Load Below-the-Fold Images
Since WordPress 5.5, the loading="lazy" attribute is added automatically to images rendered through core functions. This tells the browser to defer fetching images that are not yet in the viewport, reducing the initial page weight and freeing up bandwidth for resources that are needed immediately.
You do not need a plugin to enable lazy loading for most images. What you do need to watch for is lazy loading being applied incorrectly to above-the-fold images, particularly the LCP element. A lazy-loaded hero image will hurt your LCP score because the browser deliberately delays its fetch. More on fixing that in the next step.
For images embedded outside standard WordPress functions (custom HTML, ACF fields rendered manually, shortcodes in some plugins), verify the attribute is present in the rendered source. Add it explicitly where it is missing:
<img src="..." alt="..." width="800" height="500" loading="lazy">Step 5: Prioritize the Hero Image with fetchpriority
LCP must be at or below 2.5 seconds for a passing score. On most marketing pages and blog posts, the hero image or featured image is the LCP element. The browser’s preload scanner will discover it, but by default it competes with other resources at normal priority. You can tell the browser to treat it as high priority with a single attribute:
<img
src="hero.webp"
alt="Descriptive text here"
width="1920"
height="1080"
fetchpriority="high"
decoding="async"
>A few rules to apply this correctly:
- Use
fetchpriority="high"on at most one or two images per page, ideally just one. Prioritizing everything prioritizes nothing. - Do not combine
fetchpriority="high"withloading="lazy"on the same element. They conflict: lazy loading defers the fetch, high priority accelerates it. The browser behavior in this combination is inconsistent across versions. - You can also add a
<link rel="preload">hint in the document head for the hero image, which gets the fetch started even earlier, before the browser has parsed the body. This is especially useful if the image URL is known at render time.
<link rel="preload" as="image" href="hero.webp" fetchpriority="high">In WordPress, adding these attributes to the featured image or a custom hero block typically requires a small filter or a block variation. Many optimization plugins expose a setting for this; look for options labeled “LCP image priority” or similar.
Step 6: Let the Delivery Layer Do Its Part
Even a perfectly optimized image benefits from being delivered close to the visitor. A CDN with edge caching, like Cloudflare, caches your WebP and AVIF variants at edge nodes globally so that repeat visitors fetch from a nearby server rather than your origin. Ensure your cache rules include image file extensions and that format variants are cached separately by including the Accept header in your cache key if your CDN supports it.
HTTP/3, available through Cloudflare and other modern CDNs, reduces connection overhead, which is particularly helpful when a page loads several images simultaneously.
On the server side, confirm your images are served with long Cache-Control max-age headers (a year is common for fingerprinted assets) so browsers do not re-request unchanged images on subsequent visits. If you are on a host running LiteSpeed, the LiteSpeed Cache plugin handles browser cache headers for static assets with no manual configuration required.
Measuring Whether It Worked
Always measure before and after any change. PageSpeed Insights gives you both lab data (from a single simulated run) and field data (from real Chrome users via CrUX). They tell different stories and you need both. Lighthouse in Chrome DevTools is useful for local iteration, but its scores will differ from field data because network and device conditions vary.
For images specifically, look at these diagnostics in PageSpeed Insights:
- “Serve images in next-gen formats” confirms WebP or AVIF is not yet in place.
- “Properly size images” flags oversized images relative to their rendered dimensions.
- “Image elements do not have explicit width and height” points to potential CLS contributors.
- “Largest Contentful Paint element” identifies what the browser considers the LCP element, which confirms whether your
fetchprioritytarget is correct.
Putting It All Together
The image pipeline is not a single setting you flip. It is a sequence of decisions: convert to WebP and AVIF, size images correctly for each viewport, set explicit dimensions to hold layout, lazy load everything below the fold, and push the hero image to the front of the loading queue with fetchpriority="high". Then let your CDN carry those optimized assets to visitors quickly.
Work through the steps in order, measure after each change, and you will have a clear view of exactly how much each one contributed. That is how image optimization actually moves the needle.