Next.js Image Optimization — next/image Component Guide
The Next.js Image component automatically optimizes images: serving WebP/AVIF, lazy loading, preventing layout shift with explicit dimensions, and resizing for responsive...
The next/image component wraps <img> with automatic format conversion (WebP/AVIF), lazy loading, and layout-shift prevention. Using it correctly eliminates most image-related Lighthouse warnings.
Compress images before importing with the Image Compressor.
Basic usage
import Image from 'next/image';
// Local image (width/height inferred from file):
import heroImage from '@/public/hero.jpg';
export default function Hero() {
return (
<Image
src={heroImage}
alt="Hero image"
priority // Above-the-fold images: remove lazy loading
/>
);
}
// Remote image (width/height required):
export default function Avatar({ user }) {
return (
<Image
src={user.avatarUrl}
alt={user.name}
width={64}
height={64}
/>
);
}
width and height props
These don’t set the rendered size — they set the aspect ratio and prevent layout shift:
// Rendered at CSS width/height, but aspect ratio from props:
<Image
src="/photo.jpg"
width={800}
height={600}
alt="Photo"
style={{ width: '100%', height: 'auto' }} // Fill container
/>
// Or use Tailwind:
<Image
src="/photo.jpg"
width={800}
height={600}
alt="Photo"
className="w-full h-auto"
/>
Without width/height, the browser doesn’t know the image size until it loads, causing Cumulative Layout Shift (CLS).
fill: responsive images that fill a container
// fill mode: image fills parent — parent must have position: relative
<div className="relative w-full h-64">
<Image
src="/landscape.jpg"
alt="Landscape"
fill
sizes="100vw"
className="object-cover" // or object-contain
/>
</div>
// Product card with fixed aspect ratio:
<div className="relative aspect-square w-48">
<Image
src={product.image}
alt={product.name}
fill
sizes="192px"
className="object-contain p-4"
/>
</div>
sizes: tell the browser which size to download
sizes prevents downloading a 1200px image for a 300px thumbnail:
// Without sizes: browser might download the largest image
<Image src="/photo.jpg" fill alt="Photo" />
// With sizes: browser calculates which srcset entry to download
<Image
src="/photo.jpg"
fill
alt="Photo"
sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw"
/>
// At 400px viewport: downloads the ~400px version
// At 1400px viewport in 3-column grid: downloads the ~467px version
priority: above-the-fold images
// Hero image: no lazy loading, starts fetching immediately
<Image
src="/hero.jpg"
alt="Hero"
width={1200}
height={600}
priority
/>
// Rule: mark priority on the Largest Contentful Paint (LCP) image
// Without priority: lazy loading defers LCP image = worse performance score
Only the first 1-2 above-fold images need priority. Using it on every image defeats lazy loading.
Remote image domains
Next.js blocks remote images by default to prevent abuse:
// next.config.js (Next.js 13+ with remotePatterns):
module.exports = {
images: {
remotePatterns: [
{
protocol: 'https',
hostname: 'images.unsplash.com',
},
{
protocol: 'https',
hostname: '**.cloudinary.com', // Wildcard subdomain
},
{
protocol: 'https',
hostname: 's3.amazonaws.com',
pathname: '/my-bucket/**', // Restrict to specific path
},
],
},
};
Custom loader for CDNs
// next.config.js: Cloudinary loader
module.exports = {
images: {
loader: 'custom',
loaderFile: './lib/cloudinary-loader.js',
},
};
// lib/cloudinary-loader.js
export default function cloudinaryLoader({ src, width, quality }) {
const params = [
'f_auto', // Auto format (AVIF/WebP)
'c_limit', // Don't upscale
`w_${width}`,
`q_${quality || 75}`,
];
return `https://res.cloudinary.com/my-cloud/image/upload/${params.join(',')}/${src}`;
}
// Now Image component uses Cloudinary URLs:
<Image
src="products/shirt.jpg" // Just the path, no domain
width={400}
height={400}
alt="Shirt"
/>
// → https://res.cloudinary.com/my-cloud/image/upload/f_auto,c_limit,w_400,q_75/products/shirt.jpg
Placeholder blur (low-quality image placeholder)
import Image from 'next/image';
import heroImage from '@/public/hero.jpg';
// Local images: blurDataURL generated automatically
<Image
src={heroImage}
alt="Hero"
placeholder="blur"
/>
// Remote images: provide blurDataURL manually (base64 tiny placeholder)
// Generate with: https://blurha.sh or plaiceholder npm package
<Image
src="https://example.com/photo.jpg"
width={800}
height={600}
alt="Photo"
placeholder="blur"
blurDataURL="data:image/jpeg;base64,/9j/2wBDAAgGBgcGBQgHBwcJCQgKDBQNDAsLDBkSEw8UHRofHh0aHBwgJC4nICIsIxwcKDcpLDAxNDQ0Hyc5PTgyPC4zNDL/..."
/>
Lighthouse checklist
| Issue | Solution |
|---|---|
| Missing width/height | Add explicit width/height props |
| LCP image lazy loaded | Add priority prop |
| Oversized image | Add sizes prop matching CSS layout |
| No WebP/AVIF | Use next/image (automatic) |
| Layout shift | Add width/height or use fill with sized parent |
Related tools
- Image Compressor — reduce file size before Next.js optimization
- AVIF Format Guide — understand Next.js AVIF support
- Aspect Ratio Calculator — calculate correct width/height ratios
Related posts
- AVIF Image Format — Better Compression Than WebP and JPEG — AVIF offers 50% smaller files than JPEG at equivalent quality. Learn browser sup…
- Compress JPEG Online — Reduce Image File Size Without Losing Quality — JPEG compression lets you reduce image file sizes by 40–80% with minimal visible…
- Image Compression Formats — JPEG, PNG, WebP, AVIF Compared — JPEG, PNG, WebP, and AVIF compress images differently. JPEG is lossy and best fo…
Related tool
Compress JPEG, PNG, and WebP images in your browser. Adjustable quality, batch mode. Files never leave your device.
Written by Mian Ali Khalid. Part of the Dev Productivity pillar.