next/image
does a lot automatically — format conversion, lazy , preventing layout shift. But several props change behavior significantly, and getting them wrong either hurts performance or breaks things in production. Here's the complete picture of what actually matters, with the approach used in the image-heavy generation results interface.
When you use <Image>
from next/image
, you get automatically:
sizes
Prop — The Most Underused Optimization
The sizes
prop tells the browser what size the image will be at different viewport widths. Without it, Next.js can't serve optimally-sized images and defaults to generating several sizes that may be larger than needed.
// Without sizes — Next.js doesn't know the display size
<Image src="/hero.jpg" alt="Hero" fill />
// With sizes — Next.js serves the right size for each viewport
<Image
src="/hero.jpg"
alt="Hero"
fill
sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw"
/>
Read the sizes
value as: "On mobile (under 768px), this image takes 100% of the viewport width. On tablets (under 1200px), 50%. On desktop, 33%."
Next.js uses this to calculate which image size to serve. Correct sizes
values can reduce image payload by 50-70% on mobile.
Common patterns:
// Full-width hero or banner
sizes="100vw"
// Half-width on desktop, full on mobile
sizes="(max-width: 768px) 100vw, 50vw"
// Card grid — 1 col mobile, 2 col tablet, 3 col desktop
sizes="(max-width: 640px) 100vw, (max-width: 1024px) 50vw, 33vw"
// Small thumbnail — always small
sizes="150px"
priority
— For Above-the-Fold Images
By default, next/image
lazy loads everything. For images visible immediately on page load (hero images, header images, above-the-fold content), lazy is counterproductive — you want these to load as fast as possible.
// Hero image — load immediately, don't lazy load
<Image
src="/hero.jpg"
alt="Page hero"
width={1200}
height={600}
priority
/>
Add priority
to the first image visible on page load. This tells Next.js to preload it and not apply lazy .
The rule: One or two images maximum per page should have priority
. If you add it to many images, you've eliminated the performance benefit of lazy .
fill
vs Width/Height — When to Use Each Explicit width/height: Use when you know the exact dimensions at render time. Generates precise sizes.
<Image
src="/thumbnail.jpg"
alt="Product thumbnail"
width={300}
height={200}
/>
** fill mode:** Use when the image should fill its container. The container must be
position: relative
(or absolute
or fixed
).
<div className="relative h-64 w-full">
<Image
src="/cover.jpg"
alt="Article cover"
fill
sizes="100vw"
className="object-cover"
/>
</div>
className="object-cover"
on the Image determines how it fills the container. object-cover
maintains aspect ratio and crops. object-contain
maintains aspect ratio with letterboxing.
quality
— When Default Isn't Right Default quality is 75, which is a good balance. For specific cases you might adjust:
// Photographic images — default 75 is fine
<Image src="/photo.jpg" alt="Photo" width={800} height={600} />
// Icons or images with sharp lines — higher quality prevents artifacts
<Image src="/logo.png" alt="Logo" width={100} height={50} quality={90} />
// Background textures where quality difference isn't visible
<Image src="/texture.jpg" alt="" width={1200} height={800} quality={60} />
Higher quality = larger file size. The default is right for most cases.
remotePatterns
Config
By default, next/image
only optimizes images from your own domain. For external images, you need to configure allowed patterns:
// next.config.js
/** @type {import('next').NextConfig} */
const nextConfig = {
images: {
remotePatterns: [
{
protocol: 'https',
hostname: 'cdn.example.com',
port: '',
pathname: '/images/**',
},
{
protocol: 'https',
hostname: '*.cdn-example.com', // wildcard subdomain
},
],
},
};
module.exports = nextConfig;
Be specific with pathname
patterns. Wildcard /**
allows any path on that host. More restrictive patterns are safer.
placeholder
Prop — Blur-Up
For a smooth experience, next/image
supports a blur placeholder that shows while the full image loads:
// Static images — Next.js generates blurDataURL automatically at build time
import heroImage from '/public/hero.jpg';
<Image
src={heroImage}
alt="Hero"
placeholder="blur"
priority
/>
For dynamic images (external URLs), you need to provide the blur data yourself:
// Generate a tiny blurred placeholder (many services provide these)
const blurDataURL = 'data:image/jpeg;base64,/9j/4AAQ...'; // tiny base64 image
<Image
src={dynamicImageUrl}
alt="Dynamic image"
width={800}
height={600}
placeholder="blur"
blurDataURL={blurDataURL}
/>
The placeholder="blur"
approach significantly improves perceived performance for above-the-fold images.
To verify your image optimization is working:
Img
Type
column — should show webp
or avif
instead of jpeg
/png
Size
column vs original file size A well-optimized image should be significantly smaller than the original and in a modern format.Forgetting sizes on fill images. Without
sizes
, Next.js can't optimize for viewport — generates full-size images for all viewports.Adding priority to too many images. It's for above-the-fold only. One or two per page maximum.
Not configuring remotePatterns. External images without pattern configuration fall back to unoptimized serving, breaking the performance benefits.
Using <img> instead of <Image>. Native
<img>
tags don't get any next/image optimization — no format conversion, no lazy , no layout stability. ESLint rule next/no-img-element
catches this.| Prop | When to use |
|---|---|
sizes |
Always, when using fill or when image width changes across viewports |
priority |
First 1-2 visible images on page load only |
fill |
When image should fill a container with unknown dimensions |
quality |
When default 75 is visibly too low (icons) or unnecessarily high |
placeholder="blur" |
Above-the-fold images where is visible to users |
The three that move the needle most: correct sizes
(biggest file size impact), priority
on LCP images (biggest perceived performance impact), and remotePatterns
configuration (required for external images to optimize at all).
If you serve images from a custom CDN with its own optimization parameters, you can configure a custom :
// next.config.js
module.exports = {
images: {
: 'custom',
File: './image.js',
},
};
// image.js
export default function myImage({ src, width, quality }) {
return `https://your-cdn.com/image?url=${src}&w=${width}&q=${quality || 75}`;
}
This gives you full control over how image URLs are constructed for optimization, useful when your CDN uses different query parameter formats than Next.js expects.
For most applications using standard hosting, the default is correct. Custom s are for specific CDN integrations that need URL format control.
If you're on an older Next.js version using domains
config, migrate to remotePatterns
:
// Old (deprecated)
images: {
domains: ['cdn.example.com'],
}
// New (use this)
images: {
remotePatterns: [
{ protocol: 'https', hostname: 'cdn.example.com' }
],
}
remotePatterns
is more secure — it supports pathname restrictions that domains
doesn't, preventing a compromised external hostname from serving arbitrary paths through your Next.js image optimizer.