CrawlCheck

Findings · 2026-09-09 · By

Lazy-load placeholders: why image checks miss your photos

A lazy-load plugin puts a placeholder in src and the real URL one attribute over. Tools that read src see no photographs. Ours did: 14 placeholders, zero WebP files, on a page of 13 WebP photos.

Lazy-loading plugins such as WP Rocket and Perfmatters put a tiny inline placeholder in an image's src and the real address in an attribute such as data-lazy-src, then swap it in with JavaScript. Any tool that reads raw HTML and trusts src measures the placeholder, not your photo. Ours did: on one page serving 13 WebP photos it counted 14 inline images and no WebP files. To check your own site, count images with a data URI in src and how many carry a real URL in a lazy-load attribute.

If your site uses a lazy-load plugin, the src on most of your images probably holds a tiny inline placeholder, not your photograph. Any tool that reads raw HTML without running JavaScript, including audit tools and language models summarising your page, measures the placeholder unless it knows where the plugin moved the real URL. Our own image check made exactly this mistake, and the numbers below show how far off it was.

How do you check your own markup? #

  1. Open your homepage source (view source, not the inspector) and find your hero image.
  2. Look at what is inside src. If it starts with data:, it is a placeholder.
  3. Count the image tags with a data URI in src, then count how many of those carry an attribute such as data-lazy-src or data-src holding a real URL.
  4. If the two counts match, every tool reading src naively is wrong about your images: your file formats, your image weight, and sometimes your alt coverage.

Before trusting any tool’s image counts, check which attribute it reads. A placeholder is a perfectly valid string, so a tool that takes it at face value reports no error; it just reports the wrong thing.

The image tag as servedsrc = "data:image/svg+xml…" (placeholder)data-lazy-src = "…/fence.webp" (real photo)Reads src only: sees a placeholderReads the lazy-load attribute: sees the photoA browser running JavaScript swaps the real URL in. A crawler or audit tool reading raw HTML does not,unless it knows which attribute to read.
The real address is in the markup, one attribute over. Whether a tool finds it decides what it reports about your images.

What the placeholder pattern looks like #

WP Rocket, Perfmatters and most lazy-load implementations work the same way: the browser gets a placeholder so nothing blocks the first paint, and JavaScript swaps in the real URL as the image nears the viewport. The page hides nothing. On one Denver fencing site we counted the markup by hand: 28 <img> tags. Fourteen carried src="data:image/svg+xml...", a placeholder a few hundred bytes long. Thirteen of those also carried data-lazy-src with the real URL, and all thirteen ended in .webp. The fourteenth was a genuine inline SVG icon with an empty alt, correct markup for decoration.

What our check recorded, before and after the fix #

On 9 September we read the image data CrawlCheck stores for the WordPress sites it measures daily. Our reader took src at face value, so on that fencing site it recorded 14 inline images and zero modern-format files, on a page built from 13 WebP photographs. Here is the same reader before and after the fix, run against the live homepages:

SiteData URIs beforeModern format beforeData URIs afterModern format after
Fencing site, Denver140113
Fencing site, second city14017
Fencing site, third city6014
Tree-service site A (control)0808
Tree-service site B (control)1717
Online shop (control)3333

The last three rows are controls, and they matter more than the first three. Those sites run cache setups that do not use placeholders, so they read correctly before and identically after. A fix that moved every site would have been a second bug.

The second fencing site is the honest row. It has 13 real image URLs behind placeholders, but six are .jpg, so reading the right attribute raises its modern-format count to 7, not 13. Reading the correct attribute does not invent an optimisation the site has not done.

Why count 15 images, not 28? #

Lazy-load plugins also emit a <noscript> copy of every image they defer, so a reader without JavaScript still gets the picture. Counting both copies doubles every lazy-loaded site. On the Denver page, counting each image once gives 15: fourteen deferred images plus the icon. If a tool reports roughly twice the images you think you have, this is the likely reason.

What changed #

When src holds a placeholder, our check now reads the real URL the plugin left in the markup. An image with nothing behind it but a data URI still counts as one, which is why the fencing sites read 1 rather than 0: that one is a real inline SVG. No site’s grade changed. That is the uncomfortable part: a wrong number that moves no score is still wrong, and harder to catch precisely because nothing looks broken.

One more disclosure. Our open-source extraction of this reader already had the correct behaviour, because writing the package meant writing tests against real markup. The live service caught up five days later.

Why tools keep making this mistake #

This was the third defect of the same shape in ten days. In one, a reader that required quoted attribute values missed a viewport tag on valid minified HTML, written up in the viewport was there all along. In another, real alt text read back as empty and described images were counted as decorative. Each time, the parser read an attribute at face value on a page where something, a minifier or a performance plugin, had rewritten the markup between the author and the browser.

Self-checks do not catch this class of error, because both halves of the comparison inherit the same blind spot: when the reader believes a page has no modern images, everything downstream agrees. Only reading the live page by hand settles it. We hit the same trap in our proofs said pending for 26 days, where monitoring confirmed a job ran and never opened what it produced. The corrected readings for the sites we measure are in the public directory.

The main product

Found this on your own site? We fix it for $100.

Scan free to see where you stand. The fix is one site, every finding implemented and re-measured, with a sealed before and after.

Free browser extension

Check this on any site you open, without leaving the page.

CrawlCheck for Chrome grades every site you visit A–F and shows, from your own browser, what search engines and AI assistants are actually served. Free, no account.

Questions this post answers

Why does an audit tool say my images are not WebP when they are?

Usually because a lazy-load plugin put an inline placeholder in src and the real WebP URL in an attribute such as data-lazy-src. A tool that reads only src, without running JavaScript, sees the placeholder and reports its format instead of your photo's.

How do I check whether my images use lazy-load placeholders?

View the page source, look at src on your images, and count how many start with data:. Then count how many of those also carry a lazy-load attribute holding a real URL. If the counts match, tools reading src are measuring placeholders.

Do lazy-load placeholders hurt crawlers?

The real URL is still in the markup, so nothing is hidden, but any reader that does not run JavaScript and does not know the lazy-load attribute will see a placeholder. That includes some audit tools and some systems that summarise raw HTML.

Why does a tool report twice as many images as my page has?

Lazy-load plugins add a noscript copy of each deferred image so readers without JavaScript still get it. A tool that counts both the deferred tag and its noscript copy doubles the total.

Did this error change any CrawlCheck grades?

No. It made the image counts in reports wrong, not the grades, which is why it was easy to miss. The check now reads the real URL from the lazy-load attribute, and sites without placeholders read the same as before.

Related findings

Comments

Comments are read before they appear. Nothing is published automatically, and no account is needed.

Writing about this? Facts, live figures and marks — every number on that page is dated and traceable to a scan.

All findings · The dataset · How the dataset works