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:
-
Remove the
loading="lazy"
prop: Next.js Image component has lazy loading enabled by default, so addingloading="lazy"
is redundant and can cause conflicts. -
Use
priority={true}
for above-the-fold images: Images that should load immediately (like hero images) need thepriority
prop. -
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.