Get a Free Consultation

Lazy Loading Pitfalls: When It Hurts Your Core Web Vitals Instead of Helping

Lazy Loading Pitfalls When It Hurts Your Core Web Vitals Instead of Helping
Lazy Loading Pitfalls When It Hurts Your Core Web Vitals Instead of Helping
Published
Last reviewed
Reading time10 min read

The most damaging lazy loading pitfalls for Core Web Vitals share one cause: deferring something the browser needed immediately. The rule that avoids most of them is simple. Lazy-load images below the first screen, never lazy-load the Largest Contentful Paint (LCP) image, and reserve space for every deferred element so the layout does not jump when it arrives.

In this article19 sections

This matters because the mistake is common. According to HTTP Archive’s 2025 Web Almanac performance chapter, an image is the LCP element on roughly 76% of mobile pages, and around 16 to 17% of pages lazy-load that very image. Google’s “good” targets are an LCP of 2.5 seconds or less, an Interaction to Next Paint (INP) of 200 milliseconds or less, and a Cumulative Layout Shift (CLS) of 0.1 or less, measured at the 75th percentile of real visits. A single misplaced attribute can push a page past the first of those.

Below you will find the five pitfalls that cause the most damage, how to confirm each one, and a safe default pattern you can copy.

What Are Lazy Loading Pitfalls, and Why Do They Hurt Core Web Vitals?#

Lazy loading pitfalls are implementation mistakes that make a performance technique work against you. Lazy loading delays a resource until it is likely to be needed. When the delayed resource is something users see on first render, or when the delay changes the page’s layout, LCP and CLS get worse instead of better.

The technique itself is sound. Deferring offscreen images saves bandwidth and reduces network contention for the resources that matter. The problems start when it is applied by default, by a plugin, or by a framework, without anyone deciding which images are actually offscreen.

Pitfall 1: Lazy-Loading Above-the-Fold Images#

Should you lazy-load images above the fold? No. Any image visible when the page first renders, especially the LCP image, should load eagerly. Native lazy loading makes the browser wait until layout is known before requesting the image, which pushes the request later than it needs to be.

This is the classic above-the-fold lazy loading mistake. web.dev’s guidance on browser-level image lazy loading says to use loading=”lazy” only for images outside the initial viewport, and notes that the browser cannot lazy-load an image until it knows where it sits on the page, which makes first-screen images load more slowly.

What does the evidence show?#

The clearest public test is web.dev’s analysis, “The performance effects of too much lazy loading.” It is older data, but the finding has held up. Using HTTP Archive data, the median page without lazy loading had a 75th-percentile LCP of 2,922 ms, against 3,546 ms for the median page with it. That is correlational, because lazy loading adopters may differ in other ways.

The follow-up A/B test on WordPress is more convincing. Disabling lazy loading improved LCP on archive pages by about 13% on desktop and 15% on mobile. A fix that lazy-loaded only below-the-fold images matched that LCP gain while keeping the image-byte savings.

How do you exclude the LCP image?#

Three practical rules:

  1. Remove loading=”lazy” from the LCP image. The default value, eager, needs no attribute.
  2. Add fetchpriority=”high” to that one image.
  3. Never combine loading=”lazy” with fetchpriority=”high”. web.dev notes that such an image is still delayed while it is offscreen and only fetched at high priority as it nears the viewport.

WordPress core handles part of this automatically. Since version 6.3 it adds fetchpriority=”high” to the image it judges most likely to be the LCP image, and never pairs it with lazy loading. Its changelog cites roughly a 10% average LCP gain in lab tests. The catch is that the server guesses from markup position, not from the real viewport, so custom themes and page builders can still mislead it. Search Savvy’s WordPress speed optimization checklist is a useful companion if you run that stack.

Pitfall 2: CLS from Lazy Images Without Reserved Space#

Does lazy loading cause layout shift? Only when the image has no reserved space. If the browser does not know an image’s dimensions, it treats the image as 0 by 0 pixels until it downloads, then pushes content down when it arrives. That is CLS from lazy images in its simplest form.

web.dev recommends adding width and height attributes to every <img>, lazy or not, so the browser can reserve the right amount of space from the aspect ratio. Without them, a gallery can behave badly too: each unsized image takes no space, so the browser may decide they all fit in the viewport and load everything at once, which cancels the saving.

Fixes, in order of preference:

  • Set width and height on every image, and keep height: auto in your CSS for responsive scaling.
  • Use the CSS aspect-ratio property for containers that hold images, video poster frames, or embeds.
  • Reserve a minimum height for lazy-loaded ads, iframes, and widgets, because these are frequent culprits too.

Pitfall 3: Intersection Observer Implementation Mistakes#

Native lazy loading is supported in current versions of Chrome, Edge, Firefox, and Safari (Safari from 15.4), so JavaScript is rarely required for images. Teams still reach for the Intersection Observer API for backgrounds, components, and custom effects. That is fine, but the intersection observer implementation has traps:

  • Late discovery. Images stored in data-src are invisible to the browser’s preload scanner until your script runs. That is a second delay on top of the lazy one.
  • Observing everything. A script that targets every img[data-src] will also defer the hero.
  • A tight trigger. With no rootMargin, images start loading only when they touch the viewport, so users see blank space while scrolling. Chrome’s native behavior starts fetching well before that (web.dev cites 1250px on fast connections).
  • No fallback. If the script fails or JavaScript is off, the image never appears.

A safer version looks like this:

const observer = new IntersectionObserver((entries, obs) => {

  entries.forEach((entry) => {

    if (!entry.isIntersecting) return;

    const img = entry.target;

    img.src = img.dataset.src;

    obs.unobserve(img);

  });

}, { rootMargin: ‘1250px 0px’ });

document

  .querySelectorAll(‘img[data-src]:not(.hero)’)

  .forEach((img) => observer.observe(img));

Pair it with a <noscript> fallback, and keep dimensions on the element. Framework users should check defaults as well. Components such as Next.js’s Image lazy-load unless you mark the hero as priority, which is covered in Search Savvy’s JavaScript SEO checklist for React and Next.js.

Two behaviors catch people out:

  • display: none images do not load in Chrome, Safari, or Firefox. Hiding with opacity: 0 still triggers the download, so the choice of hiding method changes what you pay for.
  • CSS background images cannot use the loading attribute, which applies to <img> elements. A hero set as a background is also invisible to the preload scanner, so it often arrives late. If the design needs one, preload it with fetchpriority=”high”.

Pitfall 5: Lazy Loading That Hides Content from Google#

Performance is not the only risk. Google’s documentation on lazy-loaded content says Google Search does not interact with your page, so content that waits for a scroll or click may never load for the crawler. It recommends native lazy loading, the Intersection Observer API, or libraries that load content when it enters the viewport. It also advises against lazy-loading content that is likely visible immediately.

To verify, use the URL Inspection tool in Search Console and check that image URLs appear in the src attribute in the rendered HTML. For infinite scroll, give each chunk its own persistent URL.

Quick Reference: Pitfalls, Metrics, and Fixes#

PitfallMetric hurtFix
Lazy-loading the hero or LCP imageLCPRemove loading=”lazy”, add fetchpriority=”high”
Images without width and heightCLSSet dimensions or aspect-ratio
JavaScript lazy loading of first-screen imagesLCPExclude the hero, prefer native loading
Tight or missing rootMarginPerceived speed, CLSStart loading before the viewport
Scroll or click-triggered loadingIndexingUse viewport-based triggers, test rendered HTML
Hero as a CSS backgroundLCPUse an <img> or preload it

How Do You Audit Lazy Loading on Your Site?#

To check for lazy loading problems, identify your LCP element, inspect its attributes, then test the layout. Follow these steps:

  1. Run PageSpeed Insights or Lighthouse on key templates (home, category, product, article) and look for the “Largest Contentful Paint image was lazily loaded” audit.
  2. In Chrome DevTools, record a Performance trace, find the LCP marker, and check when the image request started relative to the HTML.
  3. Search the page source for loading=”lazy” on any image visible without scrolling, at both mobile and desktop widths.
  4. Check the layout shift regions in the trace for images that arrive without reserved space.
  5. Compare field data in Search Console or CrUX before and after changes, since lab scores can differ from real users.

Run this per template, not per page, because one theme setting usually repeats everywhere. Search Savvy’s guide to image optimization for Core Web Vitals covers formats and sizing once loading behavior is fixed, and the developer checklist for Core Web Vitals helps you place this fix among the other LCP, CLS, and INP tasks.

Native Lazy Loading vs Intersection Observer: Which Should You Use?#

Use the native loading=”lazy” attribute for standard images and iframes, and Intersection Observer only for needs the attribute cannot meet. Native loading needs no script, keeps the real src in the HTML, and uses browser-tuned thresholds. Intersection Observer gives control over timing and non-image work, at the cost of extra code to maintain.

A Safe Default Pattern#

<!– LCP image: eager, high priority, dimensions set –>

<img src=”hero.webp” width=”1200″ height=”600″

     fetchpriority=”high” alt=”Descriptive alt text”>

<!– Below the fold: lazy, dimensions set –>

<img src=”card.webp” width=”400″ height=”300″

     loading=”lazy” decoding=”async” alt=”Descriptive alt text”>

Hero first, everything below the fold lazy, dimensions on both.

The Bottom Line#

Lazy loading helps when it is selective and hurts when it is blanket. Keep first-screen images eager, give the LCP image high priority, reserve space for everything you defer, and confirm that Google can still see the content. If you would rather have specialists audit your templates, Search Savvy’s technical SEO services and website audit services cover this kind of review. Start with your highest-traffic template: if its LCP image carries loading=”lazy”, removing it and adding fetchpriority=”high” is often a quick fix with a measurable result.

Frequently Asked Questions#

Should I lazy-load all images on my website?#

No. Lazy-load images that start outside the first viewport. Anything visible on initial render, particularly the LCP image, should load normally. Blanket lazy loading is the most common route to a slower LCP.

Does lazy loading affect SEO?#

It can, in two ways. Poor loading behavior can worsen Core Web Vitals, and content that loads only after scrolling or clicking may not be seen by Google. Native lazy loading and viewport-based Intersection Observer setups are both safe.

Can I use fetchpriority=”high” with loading=”lazy”?#

You should not. web.dev notes the image would still be delayed while offscreen. Choose one: high priority for the LCP image, lazy loading for images further down.

How do I know if my LCP image is lazy-loaded?#

Run Lighthouse and look for the warning “Largest Contentful Paint image was lazily loaded.” You can also inspect the LCP element in Chrome DevTools and check for loading=”lazy” or a data-src pattern.

How many images should I exclude from lazy loading?#

There is no fixed number. Exclude whatever is visible on first render across common viewport sizes, usually the hero and perhaps a few images near it. WordPress uses position-based heuristics, so verify them on your own theme.

Turn insight into growth.

Our team in Mumbai helps you put ideas like these to work across social, search, content, web and paid media.

Start a conversation

Leave a Reply

Your email address will not be published. Required fields are marked *

5 ★★★★★ 11 Google reviews