Images consistently account for 50–70% of total page weight in most web applications. Next.js ships a built-in next/image component that handles the heavy lifting automatically — but using it correctly means understanding what it actually does, what configuration matters in production, and where the defaults fall short.
What next/image Actually Does
When you use next/image, requests go through Next.js's image optimization API at /_next/image. On each request it:
- Converts to WebP or AVIF (significantly smaller than JPEG/PNG)
- Resizes to the requested display dimensions
- Lazy loads by default
- Reserves layout space to prevent Cumulative Layout Shift
- Caches the optimized output server-side
Self-hosted deployments need
sharpfor server-side optimization:
npm install sharp
On Vercel, optimization runs at the edge automatically.
Basic Implementation
import Image from 'next/image'
import heroImage from '@/public/hero.jpg'
// Local image — dimensions extracted at build time
export function Hero() {
return (
<Image
src={heroImage}
alt="Descriptive alt text here"
priority
/>
)
}
// Remote image — must specify dimensions
export function ProductCard({ imageUrl }) {
return (
<Image
src={imageUrl}
alt="Product"
width={400}
height={300}
sizes="(max-width: 768px) 100vw, 400px"
/>
)
}
The sizes Prop — Most Commonly Misused
Without sizes, Next.js generates a single large image. With it, the browser receives multiple sizes and selects the correct one for the current viewport.
// Wrong — browser downloads large image regardless of display size
<Image src={src} alt={alt} width={800} height={600} />
// Correct — sizes match actual CSS rendering
<Image
src={src}
alt={alt}
width={800}
height={600}
sizes="(max-width: 640px) 100vw, (max-width: 1024px) 50vw, 800px"
/>
The sizes string describes your CSS layout. If the image is full-width on mobile, half-width on tablet, and 800px fixed on desktop, write exactly that.
fill for Flexible Containers
When you don't know dimensions ahead of time:
<div className="relative h-64 w-full">
<Image
src={imageUrl}
alt="Cover"
fill
className="object-cover"
sizes="(max-width: 768px) 100vw, 50vw"
/>
</div>
The parent needs position: relative and defined dimensions. fill makes the image absolutely positioned to match the parent.
Priority Loading for LCP
The Largest Contentful Paint image should never be lazy loaded:
// Any image visible on page load — hero, banner, above-fold card
<Image
src={heroSrc}
alt="Hero"
fill
priority
sizes="100vw"
/>
priority adds a preload link and disables lazy loading. Don't add it to off-screen images — that wastes bandwidth on content users may not scroll to.
Remote Domains Configuration
Next.js blocks external images by default. Configure remotePatterns in next.config.js:
module.exports = {
images: {
remotePatterns: [
{
protocol: 'https',
hostname: 'images.example.com',
pathname: '/uploads/**',
},
{
protocol: 'https',
hostname: '**.cloudinary.com', // wildcard subdomain
},
],
},
}
Use remotePatterns over the deprecated domains array — it gives you path-level control.
Quality, Format, and Cache Configuration
module.exports = {
images: {
quality: 80, // default 75 — increase for sharper images
formats: ['image/avif', 'image/webp'], // AVIF first, WebP fallback
deviceSizes: [640, 750, 828, 1080, 1200, 1920],
minimumCacheTTL: 60 * 60 * 24, // cache for 24h instead of default 60s
},
}
AVIF compresses better than WebP but encodes slower. Browser negotiation handles the format selection — keep both in the array.
Blur Placeholder
// Local image — blur data generated at build time automatically
<Image
src={productImage}
alt="Product"
placeholder="blur"
/>
// Remote image — provide base64 blurDataURL
<Image
src={remoteUrl}
alt="Product"
placeholder="blur"
blurDataURL="data:image/jpeg;base64,/9j/4AAQ..."
width={800}
height={600}
/>
The blur shows immediately while the full image loads. Perceived performance improves significantly even when actual load time stays the same.
Production Gotchas
Sharp is not optional for self-hosted apps. Without it, Next.js falls back to a slower path or disables optimization entirely.
Memory watch on high-traffic sites. Each unique URL/size combination gets cached. Applications serving user-uploaded images with unique filenames can exhaust cache memory. Set a reasonable minimumCacheTTL and monitor.
SVGs bypass the optimizer. SVG files are served as-is — the optimizer is designed for raster images. Handle SVGs with SVGR or as static assets.
Build-time vs request-time dimensions. Local imports have their dimensions extracted at build time. Remote URLs need width and height specified in the component or fill layout.
Measuring the Impact
Track these before and after switching to next/image:
- LCP score in Core Web Vitals (primary image performance metric)
- Total image transfer size in DevTools Network tab filtered by Img
- CLS score — proper dimension reservation eliminates most image-related layout shift
For most apps serving photographic content, correctly configured
next/imagereduces image payload by 50–70% and brings CLS to near zero.
For AI-generated imagery like the content at Pixova's free sketch generator, the WebP conversion from PNG source files typically produces 40–60% size reduction — meaningful at scale when thousands of users are loading AI-generated images daily.
Top comments (0)