The Next.js Image component automatically optimizes images, but understanding quality settings, modern formats (WebP, AVIF), responsive sizing, and blur placeholders lets you squeeze every bit of performance. These advanced techniques can reduce image sizes by 50-80% while maintaining quality, dramatically improving Core Web Vitals and user experience. Let's master advanced image optimization!
Image Formats and Conversion
Automatic Format Conversion
Next.js automatically converts images to modern formats:
Format Priority (Automatic)
- AVIF - Best compression (20-50% smaller than WebP)
- WebP - Great compression (25-35% smaller than JPEG)
- JPEG/PNG - Fallback for older browsers
import Image from 'next/image';
export function OptimizedImage() {
return (
<Image
src="/images/photo.jpg"
alt="Photo"
width={800}
height={600}
/>
);
}
// What happens:
// 1. User with modern browser → Receives AVIF (smallest)
// 2. User with older browser → Receives WebP
// 3. Very old browser → Receives original JPEG
// ✅ Automatic, no configuration needed
// ✅ Best format for each browser
// ✅ Fallback ensures compatibilityFormat Comparison
| Format | File Size | Quality | Browser Support |
|---|---|---|---|
| AVIF | Smallest (100KB) | Excellent | 90%+ (Modern browsers) |
| WebP | Small (150KB) | Excellent | 97%+ (Widespread) |
| JPEG | Large (200KB) | Good | 100% (Universal) |
| PNG | Largest (400KB) | Lossless | 100% (Universal) |
Example savings: A 200KB JPEG becomes 150KB WebP (25% smaller) or 100KB AVIF (50% smaller) with no visible quality loss. Next.js handles this automatically!
Configuring Formats
/** @type {import('next').NextConfig} */
const nextConfig = {
images: {
formats: ['image/avif', 'image/webp'],
// Default: AVIF and WebP enabled
// To disable AVIF (faster builds, larger files):
// formats: ['image/webp'],
},
};
module.exports = nextConfig;
// ✅ Default includes both AVIF and WebP
// ✅ AVIF encoding is slower but produces smallest files
// ✅ Can disable AVIF for faster builds in developmentQuality Settings
Default Quality (75%)
import Image from 'next/image';
// Default quality: 75%
export function DefaultQuality() {
return (
<Image
src="/images/photo.jpg"
alt="Photo"
width={800}
height={600}
/>
);
}
// ✅ 75% provides excellent quality
// ✅ Significant file size reduction
// ✅ Imperceptible quality loss
// ✅ Good default for most imagesCustom Quality
import Image from 'next/image';
// High quality for hero images
export function HeroImage() {
return (
<Image
src="/images/hero.jpg"
alt="Hero"
width={1920}
height={1080}
quality={90}
priority
/>
);
}
// Lower quality for thumbnails
export function Thumbnail() {
return (
<Image
src="/images/thumb.jpg"
alt="Thumbnail"
width={150}
height={150}
quality={60}
/>
);
}
// Standard quality for products
export function ProductImage() {
return (
<Image
src="/images/product.jpg"
alt="Product"
width={600}
height={600}
quality={85}
/>
);
}
// Quality guidelines:
// 90-100: Hero images, artwork (larger files)
// 75-85: Product images, photos (balanced)
// 50-70: Thumbnails, backgrounds (smaller files)Quality vs File Size
Example: 1920x1080 Photo
- Quality 100: 800KB (original)
- Quality 90: 400KB (50% smaller, imperceptible loss)
- Quality 75: 200KB (75% smaller, minimal loss)
- Quality 60: 120KB (85% smaller, acceptable loss)
- Quality 50: 80KB (90% smaller, noticeable loss)
💡 Sweet spot: 75-85 provides 70-80% size reduction with excellent quality
Responsive Images with Sizes
The sizes Prop
The sizes prop tells the browser which image size to load based on viewport width:
import Image from 'next/image';
export function ResponsiveImage() {
return (
<Image
src="/images/hero.jpg"
alt="Hero"
fill
sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw"
/>
);
}
// What this means:
// - Mobile (≤768px): Load 100vw (full width)
// - Tablet (≤1200px): Load 50vw (half width)
// - Desktop (>1200px): Load 33vw (third width)
// ✅ Browser loads appropriate size
// ✅ Smaller images on mobile
// ✅ Saves bandwidth
// ✅ Faster loadingCommon Sizes Patterns
// Full-width image (hero, banner)
<Image
src="/hero.jpg"
alt="Hero"
fill
sizes="100vw"
/>
// Half-width on desktop, full on mobile
<Image
src="/image.jpg"
alt="Image"
fill
sizes="(max-width: 768px) 100vw, 50vw"
/>
// Three columns on desktop, one on mobile
<Image
src="/image.jpg"
alt="Image"
fill
sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw"
/>
// Fixed width (doesn't change)
<Image
src="/avatar.jpg"
alt="Avatar"
width={100}
height={100}
// No sizes needed for fixed dimensions
/>
// ✅ Match sizes to actual display width
// ✅ Smaller downloads on mobile
// ✅ Better performanceContainer-Based Sizing
// Grid layout: 3 columns on desktop, 1 on mobile
export function ProductGrid() {
return (
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
{products.map(product => (
<div key={product.id} className="relative aspect-square">
<Image
src={product.image}
alt={product.name}
fill
sizes="(max-width: 768px) 100vw, 33vw"
className="object-cover"
/>
</div>
))}
</div>
);
}
// Mobile: 100vw (full width)
// Desktop: 33vw (three columns)
// ✅ Matches actual layout
// ✅ Optimal image sizesArticle Image Sizing
// Article with max-width container
export function ArticleImage() {
return (
<div className="max-w-3xl mx-auto px-4">
<Image
src="/article-image.jpg"
alt="Article illustration"
width={1200}
height={675}
sizes="(max-width: 768px) 100vw, (max-width: 1200px) 768px, 896px"
className="w-full h-auto rounded-lg"
/>
</div>
);
}
// Mobile: 100vw (full width with padding)
// Tablet: 768px (container max-width)
// Desktop: 896px (max-width: 3xl = 48rem = 768px + padding)
// ✅ Never loads larger than displayed
// ✅ Exact size for each breakpointBlur Placeholders
Why Blur Placeholders?
Blur placeholders improve perceived performance by showing something while loading:
❌ Without Placeholder
Empty space → sudden image pop-in → feels slow even if fast
✅ With Blur Placeholder
Blurred preview → gradual reveal → feels instant and smooth
Using Blur Placeholders
import Image from 'next/image';
export function ImageWithBlur() {
return (
<Image
src="/images/photo.jpg"
alt="Photo"
width={800}
height={600}
placeholder="blur"
blurDataURL="data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wBD..."
/>
);
}
// placeholder options:
// - 'empty' (default): No placeholder
// - 'blur': Show blur while loading
// ✅ Smooth loading experience
// ✅ Better perceived performance
// ✅ No empty space during loadGenerating Blur Data URLs
Several ways to generate base64 blur data:
// Method 1: Using plaiceholder library
import { getPlaiceholder } from 'plaiceholder';
async function getImage() {
const { base64 } = await getPlaiceholder('/images/photo.jpg');
return (
<Image
src="/images/photo.jpg"
alt="Photo"
width={800}
height={600}
placeholder="blur"
blurDataURL={base64}
/>
);
}
// Method 2: For imported images (automatic)
import photoImage from '../public/images/photo.jpg';
<Image
src={photoImage}
alt="Photo"
placeholder="blur"
// blurDataURL generated automatically!
/>
// Method 3: Online tools
// Use tools like:
// - https://blurha.sh
// - https://plaiceholder.co
// Generate tiny base64 blur
// ✅ Automatic for imported images
// ✅ Manual for dynamic images
// ✅ Small base64 string (~2-3KB)Blur Placeholder Example
import Image from 'next/image';
import { getPlaiceholder } from 'plaiceholder';
interface ImageData {
src: string;
alt: string;
blurDataURL: string;
}
async function getImages(): Promise<ImageData[]> {
const imagePaths = [
'/images/gallery-1.jpg',
'/images/gallery-2.jpg',
'/images/gallery-3.jpg',
];
const images = await Promise.all(
imagePaths.map(async (src) => {
const { base64 } = await getPlaiceholder(src);
return {
src,
alt: `Gallery image`,
blurDataURL: base64,
};
})
);
return images;
}
export default async function GalleryPage() {
const images = await getImages();
return (
<div className="grid grid-cols-3 gap-4">
{images.map((image, i) => (
<div key={i} className="relative aspect-square">
<Image
src={image.src}
alt={image.alt}
fill
placeholder="blur"
blurDataURL={image.blurDataURL}
className="object-cover"
/>
</div>
))}
</div>
);
}
// ✅ Blur generated at build time
// ✅ Smooth loading for all images
// ✅ Better UXAdvanced Configuration
Image Configuration Options
/** @type {import('next').NextConfig} */
const nextConfig = {
images: {
// Allowed remote image domains
remotePatterns: [
{
protocol: 'https',
hostname: 'example.com',
port: '',
pathname: '/images/**',
},
],
// Image formats (default: AVIF, WebP)
formats: ['image/avif', 'image/webp'],
// Device sizes for responsive images
deviceSizes: [640, 750, 828, 1080, 1200, 1920, 2048, 3840],
// Image sizes (for different breakpoints)
imageSizes: [16, 32, 48, 64, 96, 128, 256, 384],
// Minimum cache time (seconds)
minimumCacheTTL: 60,
// Disable static image imports
disableStaticImages: false,
// Dangerously allow SVG (use with caution)
dangerouslyAllowSVG: false,
contentSecurityPolicy: "default-src 'self'; script-src 'none'; sandbox;",
},
};
module.exports = nextConfig;
// ✅ Control optimization behavior
// ✅ Set caching policies
// ✅ Configure remote domainsCustom Loader
// Use custom CDN for image optimization
const nextConfig = {
images: {
loader: 'custom',
loaderFile: './image-loader.js',
},
};
module.exports = nextConfig;// Custom loader for external CDN
export default function myImageLoader({ src, width, quality }) {
return `https://cdn.example.com/${src}?w=${width}&q=${quality || 75}`;
}
// Use case: External image CDN (Cloudinary, Imgix, etc.)
// ✅ Leverage existing CDN
// ✅ Custom optimization parameters
// ✅ Full control over URL structurePerformance Optimization Strategies
Strategy 1: Prioritize Critical Images
// Above-the-fold hero image
<Image
src="/hero.jpg"
alt="Hero"
width={1920}
height={1080}
priority
quality={90}
sizes="100vw"
/>
// Below-the-fold images
<Image
src="/product.jpg"
alt="Product"
width={600}
height={600}
// No priority - lazy loads
quality={75}
/>
// ✅ priority for first image only
// ✅ Rest lazy load
// ✅ Optimal LCP (Largest Contentful Paint)Strategy 2: Adjust Quality by Use Case
// High-quality hero images
<Image src="/hero.jpg" quality={90} />
// Standard product images
<Image src="/product.jpg" quality={80} />
// Thumbnails and cards
<Image src="/thumb.jpg" quality={70} />
// Background images
<Image src="/bg.jpg" quality={60} />
// User avatars
<Image src="/avatar.jpg" quality={75} />
// ✅ Higher quality where it matters
// ✅ Lower quality where size matters
// ✅ Balanced approachStrategy 3: Use Appropriate Dimensions
// ❌ BAD: Loading 4K image for 400px display
<Image
src="/huge-4k-image.jpg" // 3840x2160
width={400}
height={300}
/>
// ✅ GOOD: Appropriate size
<Image
src="/optimized-image.jpg" // 800x600
width={400}
height={300}
/>
// ✅ BETTER: Let Next.js handle it
<div className="w-[400px] h-[300px] relative">
<Image
src="/any-size.jpg"
fill
sizes="400px"
/>
</div>
// Next.js generates appropriate sizes automatically
// ✅ Serves 400px-800px image (for retina)
// ✅ Not full 4K resolution
// ✅ Optimal file sizeStrategy 4: Leverage Caching
const nextConfig = {
images: {
// Cache optimized images for 60 days
minimumCacheTTL: 5184000,
},
};
// ✅ Long cache time for images
// ✅ Faster subsequent loads
// ✅ Reduced server processingMeasuring Image Performance
Core Web Vitals
Images significantly impact Core Web Vitals:
LCP (Largest Contentful Paint)
Target: <2.5s
Use priority on hero images
Optimize largest image on page
CLS (Cumulative Layout Shift)
Target: <0.1
Always provide width/height
Use fill with aspect ratio
FID (First Input Delay)
Target: <100ms
Lazy load below-fold images
Reduce initial bundle size
Testing Tools
# Lighthouse (Chrome DevTools)
# Run in Chrome DevTools → Lighthouse tab
# Check Performance score and image metrics
# WebPageTest
# https://webpagetest.org
# Detailed waterfall showing image loads
# Next.js Image Trace
# Enabled automatically in development
# Shows image optimization details in console
# Chrome DevTools Network Tab
# Filter by Img to see all image requests
# Check file sizes and load timesImage Optimization Structure
Organization for optimized images
Select a file or folder to see details
Image Optimization Best Practices
1. Use Modern Formats (Automatic)
// ✅ GOOD: Next.js serves AVIF/WebP automatically
<Image src="/photo.jpg" width={800} height={600} />
// Automatically serves AVIF (smallest) or WebP
// 30-50% smaller than JPEG
// ❌ BAD: Using regular <img> tag
<img src="/photo.jpg" />
// Always serves JPEG - larger files2. Set Appropriate Quality
// ✅ GOOD: Quality based on use case
<Image src="/hero.jpg" quality={90} /> // Hero
<Image src="/product.jpg" quality={80} /> // Product
<Image src="/thumb.jpg" quality={70} /> // Thumbnail
// ❌ BAD: Always 100% quality
<Image src="/anything.jpg" quality={100} />
// Unnecessarily large files3. Use sizes for Responsive Images
// ✅ GOOD: sizes prop for responsive
<Image
src="/image.jpg"
fill
sizes="(max-width: 768px) 100vw, 50vw"
/>
// ❌ BAD: No sizes with fill
<Image src="/image.jpg" fill />
// Loads full-size image on mobile4. Add Blur Placeholders
// ✅ GOOD: Blur placeholder
<Image
src="/photo.jpg"
width={800}
height={600}
placeholder="blur"
blurDataURL="data:image/jpeg;base64,..."
/>
// ❌ OKAY: No placeholder (but less smooth)
<Image src="/photo.jpg" width={800} height={600} />
// Works but less smooth loading5. Prioritize Above-the-Fold Images
// ✅ GOOD: priority for hero
<Image src="/hero.jpg" priority />
// ✅ GOOD: lazy load below-fold
<Image src="/below-fold.jpg" />
// No priority - lazy loads
// ❌ BAD: priority on everything
<Image src="/image1.jpg" priority />
<Image src="/image2.jpg" priority />
<Image src="/image3.jpg" priority />
// Defeats purpose of priorityKey Takeaways
- Modern formats - AVIF and WebP automatic (30-80% smaller)
- Quality settings - 75% default, adjust by use case
- sizes prop - tells browser which size to load
- Blur placeholders - improve perceived performance
- priority prop - preload above-the-fold images
- Responsive images - smaller on mobile, larger on desktop
- Core Web Vitals - optimize LCP, CLS, FID
- Configuration - customize in next.config.js
What's Next?
You've mastered advanced image optimization! Next, we'll explore Working with Static Assets—how to organize assets in the public folder, import images directly, manage different file types, and build a complete asset management strategy for your Next.js projects.
You'll learn folder organization, importing vs referencing, handling icons and fonts, and best practices for managing all static assets in Next.js.
📊 Measure Your Wins
Use Lighthouse to measure before/after. Optimized images typically improve Performance score by 10-30 points and reduce page load time by 40-60%. These are massive wins for user experience!