squirrelscan
GitHub

loading=lazy on a hero image: the LCP delay squirrel finds

An above-fold image marked loading=lazy, which delays the paint that matters. squirrel checks the first three eligible images.

What lazy loading does

loading="lazy" tells the browser to hold the request until the element comes within its loading distance of the viewport. For an image well below the fold that is exactly right: the bytes are never fetched if the visitor does not scroll that far.

For an image in the first screen it is the wrong instruction. The browser has to complete layout before it knows the element is visible, so the fetch starts later than it would have with no attribute at all. When that image is the Largest Contentful Paint element, the delay lands directly on the metric.

What squirrel checks

The rule runs on every crawled page. It walks <img> elements in document order and considers the first three that could plausibly be above the fold. It emits one check:

  • lazy-above-fold: warns with the message N above-fold image(s) with lazy loading and lists each src. Otherwise it passes with No lazy loading on above-fold images.

Three kinds of image are skipped without using up one of the three slots, so a genuinely visible image further down the markup still gets judged. An image is skipped when it or an ancestor is aria-hidden="true", hidden or inert, or carries an inline display: none; when it sits in an inactive carousel slide; or when it is inside a <footer>.

A slide counts as inactive when its group has at least two slides and it is not the active one. Active is read from an explicit marker where one exists, meaning .active, .is-active, .swiper-slide-active, .slick-active, .splide__slide--active, aria-current="true" or aria-hidden="false", and falls back to the first slide in DOM order only when no slide in the group is marked. Images whose src is a data: URI or contains pixel or spacer are skipped too, also without using a slot. A page with no parsed document emits nothing at all.

Elements inside <noscript> are skipped: a browser with scripting enabled reads <noscript> content as plain text, so nothing in it loads or renders. See content inside noscript.

How to fix it

<img src="/hero.jpg" width="1200" height="630" fetchpriority="high" alt="" />

Leave the hero with no loading attribute and add fetchpriority="high". Keep loading="lazy" for everything below the fold, which is what images/offscreen-lazy checks.

The common false positive is a page whose first three images are all genuinely below the fold, for example under a full-height banner that is a CSS background. DOM order is the only fold signal a static read has, so those images are judged as if they were visible.

Rule IDperf/lazy-above-fold
CategoryPerformance
ScopePer-page
Severitywarning
Weight5/10

Enable / disable

Disable this rule

[rules]
disable = ["perf/lazy-above-fold"]

Disable all Performance rules

[rules]
disable = ["perf/*"]

Enable only this rule

[rules]
enable = ["perf/lazy-above-fold"]

enable replaces the default ["*"], so this is the only rule selected. Leave disable out: a rule that matches any disable pattern is skipped even when enable names it. Only an enabled setting under a rule’s own [rule_options] table overrides both lists.

Performance findings ship in every audit next to the SEO, security and agent experience rules. See Core Web Vitals for how an agent works through a report.

References

Check your site

Run squirrel audit https://example.com and open the Performance section of the report. Each lazily loaded above-fold image is listed by URL. Signed-out local audits need no account and cost nothing. Signed-in audits spend credits.

Type to search…

↑↓ navigate↵ openesc close