
Most WordPress sites run at least one image optimization plugin. Many run two or three at once: a lazy load plugin, a CDN rewrite plugin, and maybe an image compression tool. The problem is that these plugins don’t coordinate—they stack transformations on top of each other, and sometimes those transformations conflict.
The result isn’t always visible. Your images might load fine on desktop, then break on mobile. Or they load, but the CDN never serves them. Or lazy load fires twice, delaying the render by an extra second.
Here’s how lazy load and CDN rewrite logic actually work, where they collide, and how to configure them so they don’t cancel each other out.
Lazy load rewrites your image tags before the page renders
Lazy load plugins work by intercepting the HTML output buffer just before WordPress sends the page to the browser. They scan for <img> tags, rewrite the src attribute to a placeholder (often a transparent 1×1 GIF or inline SVG), and move the real image URL into a data-src attribute. A JavaScript library watches the viewport and swaps data-src back into src when the image is about to scroll into view.
That rewrite happens after WordPress generates the HTML, but before it gets sent to the browser. Most lazy load plugins hook into the the_content filter or the final output buffer, which means they’re one of the last things to touch the page.
The timing matters, because CDN rewrite plugins also hook into the output buffer—and they’re looking for image URLs to rewrite.
CDN rewrite plugins replace your domain with the CDN endpoint
A CDN rewrite plugin scans the same HTML output buffer and looks for any URL pointing to your domain. When it finds an image URL like https://yourdomain.com/wp-content/uploads/image.jpg, it replaces the domain with your CDN endpoint: https://cdn.yourdomain.com/wp-content/uploads/image.jpg or https://abc123.cloudfront.net/image.jpg.
That rewrite is simple: it’s a string replacement. The plugin doesn’t parse the HTML into a DOM tree—it just searches for your domain and swaps it out.
But if the lazy load plugin has already moved the real image URL into data-src, the CDN plugin might not find it. Some CDN plugins only rewrite src attributes. Others rewrite both src and data-src, but the order matters.
When the wrong plugin runs first, images don’t serve from the CDN
If the lazy load plugin runs before the CDN rewrite plugin, the CDN plugin sees src="data:image/svg+xml…" and data-src="https://yourdomain.com/image.jpg". If it’s only configured to rewrite src, it skips the image. The image loads from your origin server, not the CDN.
You won’t see an error. The image still loads. But you’re paying for CDN bandwidth and getting none of the latency benefit.
If the CDN plugin runs before lazy load, it rewrites the src to the CDN URL, then lazy load moves that CDN URL into data-src and replaces src with a placeholder. That works—but only if the lazy load plugin doesn’t strip attributes it doesn’t recognize.
How to check which plugin runs first
View the page source in your browser. Don’t use the inspector—view the raw HTML that the server sends. Look for an <img> tag and check three things:
- Does the
srcattribute point to a placeholder or a real image? - Does the
data-src(ordata-lazy-src) attribute exist, and does it point to your CDN or your origin domain? - Are there any other
data-attributes you don’t recognize? Some plugins add their own.
If data-src points to your origin domain, lazy load is running before CDN rewrite, and the CDN isn’t serving those images.
If data-src points to your CDN, the order is correct—but you should also check the Network tab in DevTools to confirm the image actually loads from the CDN and doesn’t 404.
The fix: force CDN rewrite to run last, or use native lazy loading instead
Most WordPress lazy load plugins let you set a priority for the output buffer filter. Lower numbers run earlier. If your CDN plugin priority is set to 10 and your lazy load plugin is also 10, WordPress runs them in the order they were loaded—which is unpredictable.
Set your CDN rewrite plugin priority to something high, like 99 or 999, so it runs after lazy load. That way it catches both src and data-src attributes.
Alternatively, skip the lazy load plugin entirely and use native browser lazy loading. Add loading="lazy" to your <img> tags. WordPress has done this by default since version 5.5 for any image in post content. Native lazy loading doesn’t rewrite attributes, so there’s no conflict with CDN plugins.
The tradeoff: native lazy loading doesn’t let you control the threshold (how far from the viewport the image loads), and it doesn’t work in older browsers. But for most solo operators, that’s fine. The performance gain from avoiding two competing JavaScript libraries outweighs the loss of fine-grained control.
Want more WordPress performance breakdowns like this? Subscribe to One Two Three Send—one article like this, every day, in your inbox.
