WEBDEVJun 6, 2025·12 min read

Lazy Loading and SEO: How Modern Image Loading Patterns Affect Crawlability and Core Web Vitals

Capconvert Team

Web Development

TL;DR

Lazy loading defers off-screen image and iframe loading until the user scrolls near them. The native HTML attribute (`loading="lazy"`) handles the pattern correctly with no JavaScript and is the recommended approach in 2026. JavaScript-based lazy loading (Intersection Observer or third-party libraries) was the standard before native support but is now usually unnecessary and frequently harmful — heavy implementations damage Core Web Vitals and risk hiding content from Googlebot and AI bots that don't fully execute JavaScript. Five rules govern lazy loading for SEO and Generative Engine Optimization (GEO): use native lazy loading by default, never lazy-load the LCP image (the hero), set explicit width/height to prevent CLS, ensure srcset and modern formats work alongside lazy loading, and audit periodically because misconfigured lazy load is one of the most common hidden SEO drag patterns. The audit framework below identifies whether a site has lazy-loading problems and how to fix them.

Key Takeaways

  • -Native HTML lazy loading (`loading="lazy"` attribute) is the default in 2026 — handles below-the-fold images cleanly with no JavaScript
  • -Never lazy-load the LCP image — eager-load the hero so Largest Contentful Paint reflects the actual hero render time
  • -JavaScript-based lazy loading risks hiding content from Googlebot and AI bots that don't fully execute scripts
  • -Set explicit width and height attributes on lazy-loaded images to prevent Cumulative Layout Shift
  • -Lazy load is a common hidden SEO drag — audit periodically to confirm above-the-fold images load eagerly and below-the-fold images load lazily

Lazy loading defers off-screen image and iframe loading until the user scrolls near them. The pattern reduces initial page weight, speeds up time-to-interactive, and saves bandwidth for users who never reach the bottom of the page. Implemented correctly, lazy loading improves Core Web Vitals and reduces server load. Implemented incorrectly, lazy loading hides content from crawlers, damages LCP, produces layout shift, and adds JavaScript bloat that defeats the original purpose. The native loading="lazy" attribute (supported in all modern browsers since 2020) made the correct implementation trivial. Most sites haven't migrated to it, and the legacy implementations they're still running typically produce worse SEO and Generative Engine Optimization (GEO) outcomes than they would with default eager loading.

What Lazy Loading Is

In a typical page load, the browser fetches every <img> element and every <iframe> element regardless of whether they're visible. A 5,000-pixel-tall page with 30 images forces the browser to download all 30 before the page is fully loaded, even though the user might only see 3 of them before navigating away.

Lazy loading defers the off-screen requests. The browser fetches only images near the visible viewport at initial load and queues the rest for later. As the user scrolls, additional images load just before they enter view. The net effect:

  • Initial page weight drops 30–80% on image-heavy pages
  • LCP improves because the hero image isn't competing for bandwidth with off-screen images
  • Bandwidth costs decrease for both the user and the host
  • Time to first interaction improves because the main thread isn't blocked by image decoding

The benefits apply to most pages with images below the fold. The pattern has been recommended for performance since 2017, and Google has supported native lazy loading via the loading="lazy" attribute since Chrome 76 (2019).

Native vs. JavaScript

Two implementation approaches exist. The choice between them shapes the SEO and GEO outcome.

Native Lazy Loading

Single attribute on <img> or <iframe>:

<img src="/image.jpg" alt="..." loading="lazy" width="800" height="600">

The browser handles the rest. No JavaScript required. Works in Chrome, Edge, Firefox, Safari, and all major mobile browsers.

Why native wins:

  • Zero JavaScript overhead
  • No risk of breaking when JS fails
  • Crawlers (Googlebot, GPTBot, ClaudeBot) all see the <img> tag with src attribute in the initial HTML
  • Browser-controlled timing optimizes image loading better than user-space JavaScript can
  • Fully accessible — screen readers announce the image normally

JavaScript-Based Lazy Loading

The legacy approach. The <img> tag has no src attribute initially; instead, a data-src attribute holds the actual URL. JavaScript (often using Intersection Observer or a library like LazyLoad.js) detects when the image enters the viewport and swaps data-src to src:

<img data-src="/image.jpg" alt="..." class="lazyload">

This worked before native support existed. In 2026 it's almost always wrong.

Why JavaScript-based loses:

  • AI bots that don't execute JavaScript see <img data-src="..."> with no src — the image is invisible to them
  • Googlebot mostly handles this pattern but inconsistently — some pages have indexing issues
  • The JavaScript bundle adds 5–30KB of overhead that compounds across libraries
  • Misconfigured implementations can break entirely if the JavaScript fails to load
  • Implementation often involves CSS classes that fight with native loading attribute

The migration from JavaScript-based to native lazy loading is usually a 1-day engineering project that produces measurable Core Web Vitals lift and removes a hidden SEO drag.

Five Rules

Five rules govern lazy loading for unified SEO and GEO outcomes.

Rule 1: Use Native by Default

<img loading="lazy"> for below-the-fold images. <img loading="eager"> (or omit the attribute) for above-the-fold images. No JavaScript needed for the basic case.

Rule 2: Never Lazy-Load the LCP Image

The Largest Contentful Paint element — typically the hero image on a landing page — must load eagerly. Lazy-loading the LCP image causes the browser to defer the very element that determines LCP, producing artificially slow scores.

<!-- Wrong: lazy-loading the hero damages LCP -->
<img src="/hero.jpg" alt="Hero" loading="lazy" width="1200" height="600">

<!-- Right: eager-load the hero -->
<img src="/hero.jpg" alt="Hero" width="1200" height="600">
<!-- Or explicitly: -->
<img src="/hero.jpg" alt="Hero" loading="eager" width="1200" height="600">

For dynamic implementations (Next.js Image, frameworks with lazy-by-default), opt the LCP image out of lazy loading explicitly. The Next.js <Image priority /> prop handles this correctly.

Rule 3: Set Explicit Width and Height

Lazy-loaded images that load late shift surrounding content as the browser reserves space. Setting explicit width and height (or aspect-ratio in CSS) tells the browser how much space to reserve before the image arrives.

<img src="/image.jpg" alt="..." loading="lazy" width="800" height="600">

This single change prevents most lazy-loading-related Cumulative Layout Shift issues.

Rule 4: Combine with srcset and Modern Formats

Lazy loading and responsive images work together. Use both:

<img 
  src="/image.jpg" 
  srcset="/image-400.jpg 400w, /image-800.jpg 800w, /image-1600.jpg 1600w"
  sizes="(max-width: 600px) 400px, 800px"
  alt="..." 
  loading="lazy" 
  width="800" 
  height="600">

For modern formats (AVIF, WebP), use the <picture> element:

<picture>
  <source srcset="/image.avif" type="image/avif">
  <source srcset="/image.webp" type="image/webp">
  <img src="/image.jpg" alt="..." loading="lazy" width="800" height="600">
</picture>

Rule 5: Audit Periodically

Lazy loading is one of the most common silent SEO drags. Bugs creep in: a CMS template change disables the loading attribute; a new image variant doesn't propagate the attribute; a JavaScript framework re-introduces a legacy pattern. Run a lazy loading audit quarterly using the framework below.

The LCP Mistake

The single most common lazy loading mistake is lazy-loading the LCP image. The mistake usually happens for one of three reasons:

1. Default lazy in the framework. Some frameworks (older versions of Gatsby, certain Next.js configurations) default to lazy-loading all images. The hero image gets lazy-loaded along with the rest. LCP scores degrade by 30–70% on the affected pages.

2. CMS template that doesn't distinguish. A WordPress theme or Webflow template that adds loading="lazy" to every image globally hits the hero too. The fix requires CMS-level conditional logic.

3. Migration from JavaScript-based to native that broke priority handling. A site that previously eager-loaded the hero through custom JS migrates to a uniform loading="lazy" pattern. The migration improves most pages and breaks the hero.

The diagnostic: PageSpeed Insights shows LCP > 2.5s, the LCP element is identified as the hero image, and the hero image's HTML includes loading="lazy". The fix: change to loading="eager" or remove the attribute on the hero specifically.

For Next.js projects, the <Image priority /> prop is the recommended fix — it tells the framework to eager-load and <link rel="preload"> the image automatically.

CLS Prevention

Cumulative Layout Shift (CLS) issues from lazy loading happen when images load late and push surrounding content. Two patterns prevent this.

Pattern A: Explicit width and height attributes.

<img src="/image.jpg" width="800" height="600" loading="lazy" alt="...">

The browser reserves the aspect ratio (here 800:600) immediately, before the image loads. The image fills the reserved space when it arrives — no shift.

Pattern B: CSS aspect-ratio.

.responsive-image {
  aspect-ratio: 800 / 600;
  width: 100%;
  height: auto;
}

The CSS reserves the space using modern aspect-ratio property. Useful for fully responsive layouts where the image stretches to container width.

What to avoid:

  • width="auto" and height="auto" (browser doesn't know what to reserve)
  • Images styled only with max-width: 100% and no aspect-ratio (browser reserves nothing)
  • Images inside containers that depend on their height to size

The Lighthouse CLS audit identifies images that produce shift. Fix the top offenders first; the CLS score improves measurably with each fix.

srcset and Modern Formats

Lazy loading works alongside responsive image patterns. Modern image delivery uses both.

The full pattern:

<picture>
  <source 
    type="image/avif" 
    srcset="/image-400.avif 400w, /image-800.avif 800w, /image-1600.avif 1600w" 
    sizes="(max-width: 600px) 400px, 800px">
  <source 
    type="image/webp" 
    srcset="/image-400.webp 400w, /image-800.webp 800w, /image-1600.webp 1600w" 
    sizes="(max-width: 600px) 400px, 800px">
  <img 
    src="/image-800.jpg" 
    srcset="/image-400.jpg 400w, /image-800.jpg 800w, /image-1600.jpg 1600w" 
    sizes="(max-width: 600px) 400px, 800px"
    alt="..." 
    loading="lazy" 
    width="800" 
    height="600">
</picture>

This delivers AVIF (smallest) to browsers that support it, WebP to others, and JPEG as a final fallback. The sizes attribute tells the browser which srcset candidate to pick based on the rendered width.

Framework helpers. Most modern frameworks abstract this away. Next.js <Image />, Astro <Image />, Webflow's responsive image system, and similar all generate this pattern automatically. Opt-in to framework helpers rather than hand-rolling for routine cases; hand-roll only when the framework's defaults don't fit.

AI Bot Considerations

AI bots have specific lazy loading characteristics worth understanding.

Native lazy loading is safe for AI bots. GPTBot, ClaudeBot, PerplexityBot, OAI-SearchBot, and Google-Extended all see <img src="..." loading="lazy"> in the initial HTML. The src attribute is present and crawlable. The loading attribute doesn't change what the bot sees in the source.

JavaScript-based lazy loading is risky. AI bots that don't execute JavaScript see <img data-src="..."> without a real src. The image is effectively invisible to them. AI engines that depend on image alt text and surrounding context to understand visual content miss it entirely.

Above-the-fold images get cited more. AI bots that read the initial HTML focus on early content first. Above-the-fold images with descriptive alt text get cited disproportionately. Below-the-fold images, even when crawled, contribute less to citation context. This argues for placing the most important visual content above the fold when possible.

llms.txt should reference image-rich pages. When llms.txt or llms-full.txt references key pages, the AI bot has a clearer signal of which pages matter. Lazy-loaded images on those pages need to be reachable via initial HTML for the citation context to include them.

Lazy Loading Audit

A 9-point lazy loading audit covers most SEO/GEO failure modes:

  1. Inventory all image elements. Crawl with Screaming Frog (or extract from HTML manually) and list every <img> tag with its loading attribute and viewport position.

  2. Identify the LCP image on top pages. Run PageSpeed Insights on top 10 pages; record which element is reported as LCP. Confirm it doesn't have loading="lazy".

  3. Identify any JavaScript-based lazy loading. Search the codebase for data-src, lazyload classes, or third-party lazy loading libraries. Migrate to native unless there's a specific reason not to.

  4. Verify width/height attributes. Sample 50 lazy-loaded images and confirm they have explicit dimensions (or aspect-ratio CSS). Fix any that don't.

  5. Check for missing srcset. Lazy-loaded images that ship a single resolution waste bandwidth on small screens. Confirm srcset is implemented for content images.

  6. Run Lighthouse CLS audit. Identify images contributing to layout shift. Fix the top offenders.

  7. Test with JavaScript disabled. View key pages with JS off in browser dev tools. Confirm images render correctly (this is what crawlers without JS see).

  8. Test with mobile user agent. Mobile network constraints make lazy loading more impactful. Confirm Core Web Vitals scores on mobile.

  9. Check Search Console "Indexing > Pages". Look for "Discovered, currently not indexed" warnings on image-heavy pages — sometimes a sign of lazy loading interfering with indexing.

The audit takes 4–6 hours for a typical mid-market site. The findings often produce 0.3–0.8s of LCP improvement and meaningful CLS reduction.

Common Mistakes

Six lazy loading mistakes consistently produce worse SEO and GEO outcomes.

1. Lazy-loading the LCP image. Discussed above. The single most common LCP-related mistake.

2. Skipping width/height attributes. Causes CLS issues. Trivial fix; high impact.

3. Using JavaScript-based lazy loading in 2026. Native support is universal. JavaScript-based implementations create more problems than they solve.

4. Lazy-loading iframes that should load eagerly. Critical iframes (embedded videos in the hero section, payment processors above the fold) shouldn't be lazy-loaded. Default to lazy for below-the-fold iframes only.

5. Disabling lazy loading globally to "fix" CLS. Disabling lazy loading defeats the purpose. The correct fix is width/height attributes or aspect-ratio CSS.

6. Forgetting to audit after framework upgrades. Framework version bumps occasionally change default lazy loading behavior. Re-audit after major version upgrades.


Want a lazy loading audit for your site? Request a free AEO audit. Our team will run the 9-point checklist, identify the highest-leverage fixes, and deliver a prioritized roadmap within 5–7 business days. Capconvert has audited image loading patterns across 300+ clients since 2014 — and the framework above is the structure we use on every WEBDEV engagement that takes Core Web Vitals seriously.

Ready to optimize for the AI era?

Get a free AEO audit and discover how your brand shows up in AI-powered search.

Get Your Free Audit
Free Audit