We were going through the report of a 20,000-URL crawl of a news site and something did not add up.
ASSET-IMG-HEAVY, the rule that flags images over 200 KB, returned zero findings.
A digital newspaper. Fifteen years of publishing articles with photos. Zero heavy images.
We went to the database directly:
SELECT COUNT(*) FROM images;
-- 0
Not one row. In the same crawl, 20,000 URLs and 18,000 HTML pages, and the images table empty.
What was happening
The site runs LiteSpeed Cache, one of the most widely installed WordPress optimisation plugins. Among other things it lazy-loads images, and to do that it rewrites the HTML like this:
<img data-lazyloaded="1"
src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxMjAwIDgwMCI+PC9zdmc+"
data-src="https://example.com/wp-content/uploads/2026/07/photo.jpg"
alt="Article caption">
The src attribute, which is where everyone looks, holds a transparent SVG encoded in base64. The
real URL sits in data-src, and the browser moves it into src when the image approaches the
viewport.
Our parser read src, found a data: URI, discarded it as not being a crawlable URL, and moved on.
Correctly, in fact: a data: URI is not an image you can request from a server. The problem is that
it did not look for a real URL sitting next to it either.
What gets lost
Everything that depends on knowing the image:
- Weight. You do not know you are serving a 1.9 MB JPEG on the home page.
- Status. An image returning 404 shows up nowhere.
- Format. You cannot say how many are still JPEG when they could be WebP.
- Count. The report says the site has no images.
And the part that annoyed us most: we were reading alt correctly, because the plugin leaves that
attribute where it was. So we were dutifully reporting images with no alternative text, on a site we
believed had no images. The report contradicted itself and nobody had noticed.
The fix
When src is a data: URI, look for the real URL in the attributes lazy-loading plugins use. In
order of how common they are:
data-src— LiteSpeed Cache, lazysizes, a3 Lazy Load, Smush, EWWWdata-lazy-src— WP Rocket, Jetpack Lazy Imagesdata-original— the classic jQuery lazyload, still alive on older sites- The first candidate in
data-srcset
We did not take that list on faith and went to check: we downloaded the home page and an article
from the real site. LiteSpeed emitted data-lazyloaded="1" with data-src and data-srcset on
45 of the 51 images on the home page.
We also decided not to read <noscript>. Many plugins leave a copy of the original <img> in
there as a fallback for browsers without JavaScript, and it looked like the more reliable source.
Two reasons against it: the pages we measured had none, and on sites that do emit it, reading it
would duplicate every image and force us to deduplicate afterwards.
The result
Same site, a 300-URL crawl with the fix in place:
SELECT COUNT(*) FROM images;
-- 5945
And on a full crawl, the rules that had been silent for months:
high ASSET-IMG-BROKEN 2,193
medium ASSET-IMG-HEAVY 43,980
Forty-three thousand images over the threshold and two thousand returning errors. On a news site that is probably the highest-impact finding in the whole audit, and it had been invisible from the start.
One of the ones that turned up:
https://example.com/wp-content/uploads/2026/07/segunda-rfef-grupo-v.png
1,927,124 bytes · limit 204,800 · used on 1 page
Almost two megabytes in a PNG that could be a 150 KB WebP.
What to do when your images are heavy
Finding them is half of it. The other half is the report telling you where to start, and there the count of pages using each image matters more than its weight:
crawlforge report crawl.sqlite --rule ASSET-IMG-HEAVY
A 1.9 MB image used on one post from three years ago is not urgent. The same image in the template
header, served on every page, is the first thing to touch. Our detail includes used_by_pages for
exactly that reason.
The order we follow: template images first, then home and category pages, and the archive last, which rarely pays off unless you have an automated process.
How to tell if this is happening to you
Whatever tool you use. Crawl a site of yours that lazy-loads images and look at the image count. If it comes back suspiciously low, or zero, you have the same problem.
The quick check against your own HTML:
curl -s https://yoursite.com/ | grep -c 'data-src='
If that returns a high number and your audit says the site has few images, you know where to look.
What we took from it
No unit test would have caught this, and none did: we had 810 of them passing. The image fixtures we
had written used <img src="/photo.jpg">, like the examples in the books.
Real HTML does not look like the examples in the books. It has plugins on top.
There is a second part we found afterwards, and it is expensive: once we started reading the images, the table went from zero to 4.4 million rows, and that uncovered a missing index in the engine. The final pass of a large crawl went to over eight hours. That deserves its own article.