Next.js Image Lazy Loading Not Working - Images Loading Immediately

I'm using Next.js Image component with loading="lazy" but images are still loading immediately instead of being lazy loaded. The images appear above the fold and should be loaded right away, but images below the fold are also loading immediately, which is causing performance issues. How can I fix this lazy loading behavior?

Solution

The issue is likely caused by incorrect usage of the loading prop or missing priority prop configuration. In Next.js, the Image component has specific behavior for lazy loading that differs from standard HTML.

Here's how to fix it:

  1. Remove the loading="lazy" prop: Next.js Image component has lazy loading enabled by default, so adding loading="lazy" is redundant and can cause conflicts.

  2. Use priority={true} for above-the-fold images: Images that should load immediately (like hero images) need the priority prop.

  3. Ensure proper sizes prop: This helps the browser determine which image size to load.

import Image from 'next/image';

// Above the fold - loads immediately
<Image
  src="/hero-image.jpg"
  alt="Hero image"
  width={800}
  height={600}
  priority={true}
  sizes="(max-width: 768px) 100vw, 800px"
/>

// Below the fold - lazy loads automatically
<Image
  src="/content-image.jpg"
  alt="Content image"
  width={400}
  height={300}
  sizes="(max-width: 768px) 100vw, 400px"
/>

The key points:

  • Don't use loading="lazy" with Next.js Image component
  • Use priority={true} for critical images
  • Always provide sizes for responsive images
  • Lazy loading is automatic for non-priority images

This approach ensures proper lazy loading behavior and better performance.

Alternative #1

If you're still experiencing issues with lazy loading, the problem might be related to viewport detection. Sometimes images load immediately because they're considered "in view" by the browser's intersection observer.

You can force lazy loading by using a custom hook that controls when images should load:

import { useState, useEffect, useRef } from 'react';
import Image from 'next/image';

function useLazyImage() {
  const [shouldLoad, setShouldLoad] = useState(false);
  const imgRef = useRef(null);

  useEffect(() => {
    const observer = new IntersectionObserver(
      ([entry]) => {
        if (entry.isIntersecting) {
          setShouldLoad(true);
          observer.disconnect();
        }
      },
      { threshold: 0.1 }
    );

    if (imgRef.current) {
      observer.observe(imgRef.current);
    }

    return () => observer.disconnect();
  }, []);

  return { imgRef, shouldLoad };
}

// Usage
function LazyImage({ src, alt, width, height }) {
  const { imgRef, shouldLoad } = useLazyImage();

  return (
    <div ref={imgRef}>
      {shouldLoad && (
        <Image
          src={src}
          alt={alt}
          width={width}
          height={height}
          sizes="(max-width: 768px) 100vw, 400px"
        />
      )}
    </div>
  );
}

This gives you complete control over when images load, regardless of Next.js's built-in behavior.

Alternative #2

Another common cause is CSS affecting the layout calculation. If your images have CSS that affects their position or visibility, it can interfere with Next.js's lazy loading detection.

Make sure your images have proper CSS:

/* Ensure images don't affect layout before loading */
.lazy-image {
  display: block;
  width: 100%;
  height: auto;
}

/* Or use aspect-ratio for consistent sizing */
.lazy-image-container {
  aspect-ratio: 16/9;
  position: relative;
  overflow: hidden;
}

And in your component:

<Image
  src="/image.jpg"
  alt="Description"
  width={800}
  height={450}
  className="lazy-image"
  sizes="(max-width: 768px) 100vw, 800px"
/>

Also, check that you're not using CSS properties like transform: translateZ(0) or will-change that can trigger immediate rendering and bypass lazy loading.

Alternative #3

If you need more granular control over lazy loading behavior, you can use the placeholder prop with a blur effect to improve perceived performance:

<Image
  src="/large-image.jpg"
  alt="Large image"
  width={1200}
  height={800}
  placeholder="blur"
  blurDataURL="data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAYEBQYFBAYGBQYHBwYIChAKCgkJChQODwwQFxQYGBcUFhYaHSUfGhsjHBYWICwgIyYnKSopGR8tMC0oMCUoKSj/2wBDAQcHBwoIChMKChMoGhYaKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCj/wAARCAABAAEDASIAAhEBAxEB/8QAFQABAQAAAAAAAAAAAAAAAAAAAAv/xAAUEAEAAAAAAAAAAAAAAAAAAAAA/8QAFQEBAQAAAAAAAAAAAAAAAAAAAAX/xAAUEQEAAAAAAAAAAAAAAAAAAAAA/9oADAMBAAIRAxEAPwCdABmX/9k="
  sizes="(max-width: 768px) 100vw, 1200px"
/>

This approach:

  • Shows a blurred placeholder while the image loads
  • Improves perceived performance
  • Works well with lazy loading
  • Provides better user experience

The blurDataURL should be a very small, base64-encoded image (1x1 pixel works well) that gets blurred as a placeholder.

Last modified: October 1, 2025
Stay in the loop
Subscribe to our newsletter to get the latest articles delivered to your inbox