Website problems

Largest image lazy-loaded

Lazy loading defers an image's fetch until it approaches the viewport. For an image already in the viewport there is nothing to defer, and the attribute does active harm: it removes the image from the browser's early discovery, so the fetch is scheduled later than it would have been with no attribute at all. When that image is the largest element painted — a hero, a product photograph, a banner — the attribute directly delays the measurement it was added to improve.

Why it is worse than no attribute

A browser scans the markup early and starts fetching images it finds, before the rest of the page has parsed.

A lazy attribute takes the image out of that scan: the fetch waits until layout has determined the image is near the viewport.

So the image is requested later than a plain one would have been, not merely at the same time.

On an image already visible, the deferral achieves nothing and the delay is pure cost.

Which makes this one of the few optimisations that reliably makes a page worse.

Where the blanket attribute comes from

A content system or plugin applying it to every image, which is the most common cause by a wide margin.

A component that adds it by default, used for every image including the hero.

An optimisation checklist item applied uniformly, on the reasonable-sounding basis that lazy loading is good.

A migration where the attribute was added in bulk to an existing template library.

In each case the intent was sound and the application was indiscriminate.

The rule, stated plainly

Never lazy-load an image visible without scrolling.

Lazy-load essentially everything below the fold, where the mechanism does what it was designed to do.

Which image is above the fold depends on the viewport, so the boundary differs between a phone and a desktop — and the hero is above it on both.

For the image most likely to be the largest painted, go further: load it eagerly and give it a high fetch priority.

An image carrying both a preload and a lazy attribute is holding two contradictory instructions, and the lazy one usually wins.

What to do instead for the hero

Remove the lazy attribute, which is the whole of the fix in most cases.

Add a high fetch priority, which schedules it ahead of other resources competing for bandwidth.

Make sure it carries width and height attributes so the space is reserved before it arrives.

Consider a preload if the image is referenced somewhere the early scan cannot see it — inside a stylesheet, or built by script.

Do not preload it as well as loading it eagerly from the markup, which fetches it twice on some browsers.

How to check it yourself

View source and find the first large image in the markup — the lazy attribute on it is the defect.

Use the browser's performance tooling to identify which element is actually the largest painted, which is occasionally not the image you expect.

Check on a phone viewport as well, since the hero is frequently a different image at that width.

Check several templates, since the attribute usually comes from one shared component.

Compare the largest paint timing with and without the attribute, which is a one-line change and an unambiguous measurement.

How VeriFixScan detects it

`images.lcp` identifies the image most likely to be the largest element painted.

`images.lazy_loading` reads the lazy attributes, and `images.above_the_fold` establishes which images appear without scrolling.

`performance.lazy_loading` reads the same declarations from the performance side, and `performance.preload` is what detects a contradictory preload.

`performance.core_web_vitals` reports the rendering measurements the attribute affects.

Above the fold is measured against one viewport, so the boundary on a different screen differs.

Why lazy loading is still worth using

On a long page most images are never scrolled to, and deferring them saves the whole transfer rather than delaying it.

The saving is largest on exactly the connections where it matters most.

The native attribute costs nothing to add and requires no script.

So the finding here is not that lazy loading is wrong — it is that applying it to everything removes its benefit at the one place it does harm.

The background-image variant of the same defect

An image set as a CSS background is discovered only after the stylesheet has been fetched and parsed, which is several round trips into the load.

So a hero implemented as a background is late by construction, with no attribute involved and no way to mark it eager.

A preload declaration is the only remedy, and it has to name the same image the stylesheet will request or the file is fetched twice.

Which is one practical argument for putting content images in markup rather than in CSS: the early scan can see them.

A background image is also invisible to the largest-paint measurement in some browsers, so the problem can be real and unmeasured at once.

Frequently asked questions

Should I lazy-load every image?
No, and doing so is this defect. Images visible without scrolling must load eagerly. A blanket attribute from a plugin or a component is the usual cause.
How much does it actually cost?
The image is removed from the browser's early scan, so the fetch starts later than with no attribute at all. The exact cost depends on the connection; the direction never favours it.
Is fetch priority a substitute for removing it?
No. Priority orders fetches that are happening; the lazy attribute decides whether one happens yet. Remove the attribute first, then raise priority.

Sources

Related

VeriFixScan crawls a site and applies its checks to every page it reaches, keeping the evidence behind each finding. Scanning one website is free.

Scan a website