Why image optimization matters
Images make up 50–70% of a page’s weight. Proper optimization critically affects loading speed, Core Web Vitals and the user experience.
Modern formats
| Format | Compression | Support | Use |
|---|---|---|---|
| JPEG | Good | 100% | Photos |
| PNG | Lossless | 100% | Graphics, transparency |
| WebP | Excellent | 97% | Universal |
| AVIF | Best | 85% | Photos, where supported |
<!-- Fallback for older browsers -->
<picture>
<source srcset="/image.avif" type="image/avif" />
<source srcset="/image.webp" type="image/webp" />
<img src="/image.jpg" alt="Description" />
</picture>
Next.js Image Component
Next.js optimizes images automatically:
import Image from 'next/image';
// Basic usage
<Image
src="/hero.jpg"
alt="Hero image"
width={1200}
height={600}
priority // For LCP images
/>
// Responsive image
<Image
src="/product.jpg"
alt="Product"
fill
sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw"
className="object-cover"
/>
// With blur placeholder
import heroImage from '@/public/hero.jpg';
<Image
src={heroImage}
alt="Hero"
placeholder="blur" // Automatic blur for static imports
/>
// Dynamic blur placeholder
<Image
src={dynamicUrl}
alt="Dynamic"
placeholder="blur"
blurDataURL="data:image/jpeg;base64,/9j/4AAQSkZJRg..."
/>
Next.js configuration
// next.config.js
module.exports = {
images: {
remotePatterns: [
{
protocol: 'https',
hostname: 'images.unsplash.com',
},
{
protocol: 'https',
hostname: 'cdn.example.com',
}
],
formats: ['image/avif', 'image/webp'],
deviceSizes: [640, 750, 828, 1080, 1200, 1920, 2048],
imageSizes: [16, 32, 48, 64, 96, 128, 256, 384],
minimumCacheTTL: 60 * 60 * 24 * 30, // 30 days
}
};
Lazy Loading
// Native lazy loading
<img src="/image.jpg" alt="..." loading="lazy" />
// Next.js Image — lazy by default
<Image src="/image.jpg" alt="..." width={800} height={600} />
// Disable lazy for important images
<Image
src="/hero.jpg"
alt="Hero"
width={1200}
height={600}
priority // Loads immediately
loading="eager"
/>
// Intersection Observer for custom logic
function LazyImage({ src, alt }) {
const [isVisible, setIsVisible] = useState(false);
const ref = useRef<HTMLDivElement>(null);
useEffect(() => {
const observer = new IntersectionObserver(
([entry]) => {
if (entry.isIntersecting) {
setIsVisible(true);
observer.disconnect();
}
},
{ rootMargin: '100px' }
);
if (ref.current) observer.observe(ref.current);
return () => observer.disconnect();
}, []);
return (
<div ref={ref}>
{isVisible ? (
<img src={src} alt={alt} />
) : (
<div className="skeleton" />
)}
</div>
);
}
Responsive Images
<!-- srcset for different screen sizes -->
<img
src="/image-800.jpg"
srcset="
/image-400.jpg 400w,
/image-800.jpg 800w,
/image-1200.jpg 1200w
"
sizes="(max-width: 600px) 100vw, 50vw"
alt="Responsive image"
/>
<!-- Art direction with picture -->
<picture>
<source
media="(max-width: 767px)"
srcset="/hero-mobile.webp"
/>
<source
media="(max-width: 1023px)"
srcset="/hero-tablet.webp"
/>
<img src="/hero-desktop.webp" alt="Hero" />
</picture>
CDN and caching
// Cloudinary URL API
function getOptimizedUrl(publicId: string, options: ImageOptions) {
const { width, height, quality = 'auto', format = 'auto' } = options;
return `https://res.cloudinary.com/demo/image/upload/` +
`w_${width},h_${height},c_fill,q_${quality},f_${format}/` +
publicId;
}
// Usage
<img src={getOptimizedUrl('products/shoe', { width: 400, height: 400 })} />
// Imgix
const imgixUrl = `/placeholders/image-placeholder.svg?w=800&auto=format,compress`;
Generating a blur placeholder
// Generate a base64 blur on the server
import sharp from 'sharp';
async function generateBlurPlaceholder(imagePath: string): Promise<string> {
const buffer = await sharp(imagePath)
.resize(10, 10, { fit: 'inside' })
.blur()
.toBuffer();
return `data:image/jpeg;base64,${buffer.toString('base64')}`;
}
// Usage with Prisma
const product = await prisma.product.findUnique({
where: { id },
select: {
image: true,
blurDataURL: true
}
});
<Image
src={product.image}
alt="Product"
placeholder="blur"
blurDataURL={product.blurDataURL}
/>
Optimization checklist
- ✅ Use WebP/AVIF with a fallback
- ✅ Specify width and height (avoiding CLS)
- ✅ Lazy loading for below-the-fold images
- ✅ priority for LCP images
- ✅ Responsive sizes for different screens
- ✅ Blur placeholder for better UX
- ✅ CDN for fast delivery
Related articles
Conclusion
Image optimization is one of the most effective ways to improve performance. Next.js Image does most of the work automatically. Use modern formats, lazy loading and a CDN for maximum speed.
Every kilobyte saved means faster loading and a better UX. Optimize images first.



