Implement Lazy Loading Images Without Hurting SEO | OpsBlu Docs

Implement Lazy Loading Images Without Hurting SEO

Use native lazy loading to improve page speed while keeping images crawlable by Googlebot.

Lazy loading defers the download of offscreen images until the user scrolls near them. This reduces initial page weight, speeds up LCP, and lowers bandwidth consumption. When implemented correctly, lazy loading has no negative impact on SEO because Googlebot renders pages with a viewport and processes lazy-loaded images.

Native Browser Lazy Loading

The simplest implementation uses the loading attribute, supported in all modern browsers:

<!-- Lazy load offscreen images -->
<img src="/product-gallery-3.webp" loading="lazy" width="400" height="300" alt="Product side view">

<!-- NEVER lazy load the LCP image -->
<img src="/hero-banner.webp" loading="eager" width="1200" height="600" alt="Hero banner" fetchpriority="high">

The browser handles all the scroll detection and loading timing automatically. No JavaScript required.

Critical Rule: Never Lazy Load the LCP Image

The single most important rule: do not apply loading="lazy" to the Largest Contentful Paint element. Lazy loading the LCP image delays its discovery and download, directly increasing your LCP time. This is the most common lazy loading mistake and can add 500ms-2s to LCP.

To identify your LCP image, run Lighthouse and check which element is flagged as the LCP element. For most pages, this is the hero image, the first product image, or the featured article image.

<!-- LCP image: eager load with high priority -->
<img
  src="/hero.webp"
  loading="eager"
  fetchpriority="high"
  width="1200"
  height="600"
  alt="Main hero image"
>

<!-- Below-fold images: lazy load -->
<img src="/feature-1.webp" loading="lazy" width="600" height="400" alt="Feature one">
<img src="/feature-2.webp" loading="lazy" width="600" height="400" alt="Feature two">

How Googlebot Handles Lazy Loading

Googlebot uses a headless Chromium renderer that scrolls the page to discover lazy-loaded content. Key facts:

  • Native loading="lazy" is fully supported by Googlebot
  • Intersection Observer-based lazy loading is also supported
  • Googlebot uses a viewport of 411x731 pixels (Nexus 5X equivalent)
  • Images visible in the initial viewport are discovered immediately; below-fold images are discovered during the rendering scroll pass

Intersection Observer for Advanced Control

When you need more control over loading thresholds or want to add fade-in animations, use Intersection Observer:

const observer = new IntersectionObserver((entries) => {
  entries.forEach(entry => {
    if (entry.isIntersecting) {
      const img = entry.target;
      img.src = img.dataset.src;
      img.removeAttribute('data-src');
      observer.unobserve(img);
    }
  });
}, {
  rootMargin: '200px 0px' // Start loading 200px before the image enters viewport
});

document.querySelectorAll('img[data-src]').forEach(img => observer.observe(img));

Set a rootMargin of at least 200px to begin loading before the user scrolls to the image, avoiding visible blank spaces.

Always Include Width and Height

Every lazy-loaded image must have explicit width and height attributes or CSS aspect-ratio to reserve space in the layout. Without dimensions, the page shifts when images load, increasing CLS:

<!-- Correct: dimensions reserve space -->
<img src="/photo.webp" loading="lazy" width="800" height="600" alt="Photo">

<!-- Also correct: CSS aspect-ratio -->
<img src="/photo.webp" loading="lazy" style="aspect-ratio: 4/3; width: 100%;" alt="Photo">

Lazy Loading iframes

Apply loading="lazy" to iframes for embedded videos, maps, and third-party widgets. A YouTube embed can add 500KB+ to initial page weight:

<iframe
  src="https://www.youtube.com/embed/VIDEO_ID"
  loading="lazy"
  width="560"
  height="315"
  title="Video title"
  allow="accelerometer; autoplay; clipboard-write; encrypted-media"
></iframe>

Auditing Lazy Loading Implementation

Use Lighthouse to check for issues. The "Defer offscreen images" audit identifies images that should be lazy loaded. The "Largest Contentful Paint element" audit flags whether the LCP image is being unnecessarily deferred. Cross-reference with the CLS audit to ensure lazy-loaded images have proper dimensions.