Lazy Loading Implementation Issues | Blue Frog Docs

Lazy Loading Implementation Issues

Diagnose and fix common lazy loading problems that can hurt performance, SEO, and user experience

Lazy Loading Implementation Issues

What This Means

Lazy loading is a technique that defers loading of non-critical resources (like images, videos, or scripts) until they're needed, typically when they're about to enter the viewport. While lazy loading can significantly improve initial page load performance, improper implementation can cause worse performance, poor user experience, and SEO issues.

Common Lazy Loading Problems

Performance Issues:

  • Lazy loading critical above-fold images (delays LCP)
  • Not preloading important resources
  • Excessive JavaScript for lazy loading
  • Layout shifts when lazy content loads

User Experience Issues:

  • Blank placeholders during scroll
  • Delayed image loading on fast connections
  • Broken layouts from missing dimensions
  • Flash of unstyled content

SEO Issues:

  • Search engines not discovering lazy-loaded content
  • Missing images in image search results
  • Delayed rendering hurts crawl budget

Impact on Your Business

Performance Impact:

  • When Done Right: Faster initial page load, better LCP, reduced data usage
  • When Done Wrong: Slower LCP (if critical images lazy loaded), poor INP, CLS issues

User Experience:

  • Good: Smooth scrolling, fast initial page load, progressive enhancement
  • Bad: Jarring content pops in, delayed images, broken layouts

SEO Impact:

  • Proper implementation: No negative impact, images indexed normally
  • Poor implementation: Lost rankings, images not indexed, content not discovered

Accessibility:

  • Good: Works with screen readers, keyboard navigation
  • Bad: Screen readers can't discover content, missing alt text

How to Diagnose

Method 1: Chrome DevTools Performance Panel

  1. Open Chrome DevTools (F12)
  2. Navigate to "Performance" tab
  3. Click record and refresh page
  4. Look for:
    • Image load timing
    • Layout shifts (red bars)
    • LCP element timing
    • JavaScript execution for lazy loading

What to Look For:

  • Images loading too late (especially LCP image)
  • Multiple layout shifts as images load
  • Excessive JavaScript execution
  • Network waterfall showing delayed image loads

Method 2: Lighthouse Audit

  1. Open Chrome DevTools (F12)
  2. Navigate to "Lighthouse" tab
  3. Run Performance audit
  4. Check for:
    • "Preload Largest Contentful Paint image"
    • "Avoid large layout shifts"
    • "Does not lazy-load offscreen images"
    • "Defer offscreen images"

What to Look For:

  • Warning about lazy-loading LCP image
  • CLS score affected by image loads
  • Opportunities to lazy-load more images
  • Images loaded but not visible

Method 3: Network Tab Analysis

  1. Open Chrome DevTools (F12)
  2. Navigate to "Network" tab
  3. Filter by "Img"
  4. Reload page and scroll
  5. Observe image loading patterns

What to Look For:

  • All images loading immediately (not lazy loading)
  • Critical images loading too late
  • Images loading before they're needed
  • Duplicate image requests

Method 4: Visual Inspection

  1. Disable JavaScript:

    • Chrome DevTools > Settings > Debugger > Disable JavaScript
    • Refresh page
    • Check if images still appear
  2. Throttle connection:

    • DevTools > Network tab > Throttling > Slow 3G
    • Scroll page slowly
    • Watch for delayed image loading
  3. Check layout shifts:

    • Use Web Vitals Extension
    • Scroll and observe CLS score
    • Watch for content jumping

What to Look For:

  • Images not visible with JavaScript disabled
  • Significant delays before images appear
  • Content jumping as images load
  • Empty placeholders during scroll

Method 5: SEO Tools

  1. Google Search Console:

    • Check Coverage report
    • Look for "Discovered - currently not indexed"
    • Check Mobile Usability issues
  2. Test with Google Rich Results Test:

  3. Screaming Frog SEO Spider:

    • Crawl your site
    • Check image discovery
    • Verify all images found

What to Look For:

  • Missing images in crawl
  • Content not discovered
  • Indexing issues
  • Mobile usability problems

General Fixes

Fix 1: Use Native Lazy Loading Correctly

The simplest and most reliable approach:

  1. Apply to below-fold images only:

    <!-- Above-fold hero image - NO lazy loading -->
    <img
      src="hero.jpg"
      alt="Hero image"
      width="1200"
      height="600"
      loading="eager"
      fetchpriority="high"
    >
    
    <!-- Below-fold images - YES lazy loading -->
    <img
      src="product-1.jpg"
      alt="Product name"
      width="400"
      height="400"
      loading="lazy"
    >
    
  2. Always specify dimensions:

    <!-- Good - prevents layout shift -->
    <img
      src="image.jpg"
      alt="Description"
      width="800"
      height="600"
      loading="lazy"
    >
    
    <!-- Bad - causes layout shift -->
    <img src="image.jpg" alt="Description" loading="lazy">
    
  3. Use aspect ratio for responsive images:

    .img-container {
      aspect-ratio: 16 / 9;
      width: 100%;
    }
    
    .img-container img {
      width: 100%;
      height: 100%;
      object-fit: cover;
    }
    
    <div class="img-container">
      <img src="image.jpg" alt="Description" loading="lazy">
    </div>
    

Fix 2: Never Lazy Load Critical Resources

Identify and prioritize critical resources:

  1. Identify LCP element:

    • Use Lighthouse to find LCP element
    • Ensure it loads immediately
    • Add fetchpriority="high" if it's an image
  2. Prioritize above-fold images:

    <!-- First 2-3 images should load immediately -->
    <img src="banner.jpg" loading="eager" fetchpriority="high">
    <img src="hero-product.jpg" loading="eager">
    <img src="featured.jpg" loading="eager">
    
    <!-- Everything else can lazy load -->
    <img src="product-10.jpg" loading="lazy">
    
  3. Don't lazy load critical CSS or JavaScript:

    <!-- Critical resources - load immediately -->
    <link rel="stylesheet" href="critical.css">
    <script src="critical.js"></script>
    
    <!-- Non-critical - defer -->
    <link rel="stylesheet" href="non-critical.css" media="print" onload="this.media='all'">
    <script src="analytics.js" defer></script>
    

Fix 3: Implement Proper Placeholder Strategy

Prevent layout shifts and improve perceived performance:

  1. Use low-quality image placeholders (LQIP):

    <img
      src="image-small.jpg"
      data-src="image-full.jpg"
      alt="Description"
      class="lazy-image"
      style="background: url('data:image/jpeg;base64,...')"
    >
    
  2. Use solid color placeholders:

    <div class="img-wrapper" style="background-color: #f0f0f0;">
      <img src="image.jpg" loading="lazy" alt="Description">
    </div>
    
  3. Use CSS aspect ratio boxes:

    .image-container {
      position: relative;
      width: 100%;
      padding-bottom: 56.25%; /* 16:9 aspect ratio */
      background-color: #f5f5f5;
    }
    
    .image-container img {
      position: absolute;
      top: 0;
      left: 0;
      width: 100%;
      height: 100%;
      object-fit: cover;
    }
    

Fix 4: Set Appropriate Loading Thresholds

Load images before they enter viewport:

  1. Native lazy loading (browser controlled):

    <!-- Browser loads ~1-2 screenfuls ahead -->
    <img src="image.jpg" loading="lazy" alt="Description">
    
  2. Intersection Observer with custom threshold:

    const imageObserver = new IntersectionObserver((entries) => {
      entries.forEach(entry => {
        if (entry.isIntersecting) {
          const img = entry.target;
          img.src = img.dataset.src;
          img.classList.add('loaded');
          imageObserver.unobserve(img);
        }
      });
    }, {
      // Load when image is 200px from viewport
      rootMargin: '200px'
    });
    
    document.querySelectorAll('img[data-src]').forEach(img => {
      imageObserver.observe(img);
    });
    
  3. Progressive loading on fast connections:

    // Load images more aggressively on fast connections
    const connection = navigator.connection || navigator.mozConnection || navigator.webkitConnection;
    const rootMargin = connection && connection.effectiveType === '4g'
      ? '500px'  // Load earlier on fast connections
      : '100px'; // Load closer on slow connections
    
    const imageObserver = new IntersectionObserver((entries) => {
      // ... observer logic
    }, { rootMargin });
    

Fix 5: Ensure SEO-Friendly Implementation

Make lazy-loaded content discoverable:

  1. Use standard img tags with src attribute:

    <!-- Good - crawlers can discover -->
    <img src="image.jpg" loading="lazy" alt="Product name">
    
    <!-- Bad - crawlers might miss data-src -->
    <img data-src="image.jpg" alt="Product name">
    
  2. Provide noscript fallback:

    <img class="lazy" data-src="image.jpg" alt="Description">
    <noscript>
      <img src="image.jpg" alt="Description">
    </noscript>
    
  3. Use schema markup for important images:

    <script type="application/ld+json">
    {
      "@context": "https://schema.org",
      "@type": "Product",
      "image": "https://example.com/product-image.jpg",
      "name": "Product Name"
    }
    </script>
    
  4. Include images in sitemap:

    <url>
      <loc>https://example.com/product</loc>
      <image:image>
        <image:loc>https://example.com/product-image.jpg</image:loc>
        <image:title>Product Image</image:title>
      </image:image>
    </url>
    

Fix 6: Handle Different Content Types

Lazy load various resources properly:

  1. Videos:

    <!-- Use poster image, lazy load video -->
    <video
      poster="thumbnail.jpg"
      controls
      preload="none"
    >
      <source src="video.mp4" type="video/mp4">
    </video>
    
  2. Iframes (YouTube, maps, etc.):

    <!-- Load iframe on click -->
    <div class="video-wrapper" data-video-id="VIDEO_ID">
      <img src="thumbnail.jpg" alt="Video">
      <button>Play Video</button>
    </div>
    
    <script>
    document.querySelectorAll('.video-wrapper').forEach(wrapper => {
      wrapper.addEventListener('click', function() {
        const iframe = document.createElement('iframe');
        iframe.src = `https://www.youtube.com/embed/${this.dataset.videoId}?autoplay=1`;
        this.replaceWith(iframe);
      });
    });
    </script>
    
  3. Background images:

    <div class="hero lazy-bg" data-bg="hero.jpg"></div>
    
    <script>
    const bgObserver = new IntersectionObserver((entries) => {
      entries.forEach(entry => {
        if (entry.isIntersecting) {
          const el = entry.target;
          el.style.backgroundImage = `url(${el.dataset.bg})`;
          el.classList.add('loaded');
          bgObserver.unobserve(el);
        }
      });
    });
    
    document.querySelectorAll('.lazy-bg').forEach(el => {
      bgObserver.observe(el);
    });
    </script>
    

Fix 7: Optimize Lazy Loading JavaScript

Keep lazy loading scripts minimal:

  1. Use native loading="lazy" when possible:

    • No JavaScript needed
    • Browser optimized
    • Automatically adapts to connection speed
  2. Minimize custom lazy loading code:

    // Minimal Intersection Observer implementation
    if ('IntersectionObserver' in window) {
      const observer = new IntersectionObserver(entries => {
        entries.forEach(entry => {
          if (entry.isIntersecting) {
            const img = entry.target;
            img.src = img.dataset.src;
            observer.unobserve(img);
          }
        });
      });
    
      document.querySelectorAll('img[data-src]').forEach(img => {
        observer.observe(img);
      });
    } else {
      // Fallback: load all images
      document.querySelectorAll('img[data-src]').forEach(img => {
        img.src = img.dataset.src;
      });
    }
    
  3. Consider removing library dependencies:

    <!-- Don't need this anymore -->
    <script src="lazysizes.min.js"></script>
    
    <!-- Use native instead -->
    <img src="image.jpg" loading="lazy" alt="Description">
    

Platform-Specific Guides

Detailed implementation instructions for your specific platform:

Platform Troubleshooting Guide
Shopify Shopify Lazy Loading Guide
WordPress WordPress Lazy Loading Guide
Wix Wix Lazy Loading Guide
Squarespace Squarespace Lazy Loading Guide
Webflow Webflow Lazy Loading Guide

Verification

After implementing fixes:

  1. Test performance:

    • Run Lighthouse (LCP should improve)
    • Check CLS score (should be minimal)
    • Verify images load at right time
    • No "lazy-loading LCP image" warning
  2. Test user experience:

    • Scroll page on slow connection
    • Verify images load before visible
    • Check for layout shifts
    • Ensure smooth experience
  3. Test SEO:

    • View page source (images should have src)
    • Test with JavaScript disabled
    • Use Google Rich Results Test
    • Check image sitemap
  4. Test accessibility:

    • Use screen reader
    • Verify alt text present
    • Check keyboard navigation
    • Ensure focus management
  5. Cross-browser testing:

    • Test in Chrome, Firefox, Safari
    • Test on mobile devices
    • Verify fallbacks work
    • Check older browsers

Common Mistakes

  1. Lazy loading the LCP image - Significantly delays largest contentful paint
  2. Not setting image dimensions - Causes layout shifts
  3. Loading images too late - Poor user experience
  4. Using data-src without src - SEO issues
  5. No noscript fallback - Accessibility issues
  6. Lazy loading all images - Above-fold images should load immediately
  7. Complex JavaScript for simple use case - Use native loading="lazy"
  8. Not testing on slow connections - May work on fast but fail on slow
  9. Ignoring CLS impact - Lazy loading can increase layout shifts
  10. Not considering screen readers - Content may not be announced properly

Browser Support

Native Lazy Loading (loading="lazy"):

  • Chrome 77+ (August 2019)
  • Edge 79+ (January 2020)
  • Firefox 75+ (April 2020)
  • Safari 15.4+ (March 2022)
  • Safari iOS 15.4+ (March 2022)

Fallback for older browsers:

// Feature detection
if ('loading' in HTMLImageElement.prototype) {
  // Browser supports native lazy loading
  // No additional code needed
} else {
  // Use Intersection Observer or library
  loadLazyLoadingPolyfill();
}

Additional Resources

// SYS.FOOTER