Next.js Image Optimization Strategies with next/image
Master the next/image component with advanced optimization strategies including responsive images, blur placeholders, CDN configuration, and performance tips.
What you'll learn
- ✓How the next/image component optimizes images automatically
- ✓Configuring responsive sizes and srcset for different viewports
- ✓Using blur placeholders and priority loading for LCP images
- ✓Setting up external image domains and custom loaders
- ✓Advanced patterns for art direction and CDN integration
Prerequisites
- •Working knowledge of [Next.js basics](/blog/nextjs-install-and-first-app)
- •Understanding of [Core Web Vitals](/blog/web-performance-core-web-vitals-guide)
The next/image component does more than resize images. It generates multiple sizes on demand, serves modern formats like WebP and AVIF, lazy-loads by default, and prevents Cumulative Layout Shift by reserving space. Understanding how to configure it properly is the difference between a fast site and one that serves 2 MB hero images to mobile users.
The basics of next/image
import Image from 'next/image';
export default function Hero() {
return (
<Image
src="/hero.jpg"
alt="Mountain landscape at sunrise"
width={1200}
height={630}
/>
);
}
This single component handles:
- Format conversion: Serves WebP or AVIF depending on browser support
- Resizing: Generates optimized versions at the requested dimensions
- Lazy loading: Only loads the image when it enters the viewport
- Layout stability: Reserves the correct aspect ratio to prevent CLS
Responsive images with sizes
The sizes prop tells the browser which image width to download based on the viewport:
<Image
src="/product.jpg"
alt="Product photo"
width={800}
height={600}
sizes="(max-width: 640px) 100vw, (max-width: 1024px) 50vw, 33vw"
/>
This generates a srcset with multiple resolutions. Without sizes, the browser defaults to downloading the full-width image regardless of how much space the image actually occupies. For a three-column grid on desktop where each image is only 33% of the viewport, you would waste bandwidth downloading images three times larger than needed.
Common sizes patterns:
// Full-width hero
sizes="100vw"
// Two-column grid
sizes="(max-width: 768px) 100vw, 50vw"
// Sidebar thumbnail
sizes="(max-width: 768px) 100vw, 200px"
// Card in responsive grid
sizes="(max-width: 640px) 100vw, (max-width: 1024px) 50vw, 25vw"
Fill mode for unknown dimensions
When the image dimensions are not known at build time, use the fill prop:
<div className="relative w-full aspect-video">
<Image
src={post.coverImage}
alt={post.title}
fill
className="object-cover rounded-lg"
sizes="(max-width: 768px) 100vw, 720px"
/>
</div>
The fill prop makes the image fill its parent container. The parent must have position: relative (or absolute/fixed). Always pair fill with an explicit sizes prop, otherwise the browser assumes the image is full-width.
Priority loading for LCP images
The Largest Contentful Paint (LCP) image should load as fast as possible. Add the priority prop to disable lazy loading and preload the image:
<Image
src="/hero-banner.jpg"
alt="Summer collection"
width={1920}
height={1080}
priority
sizes="100vw"
/>
Only use priority for above-the-fold images that are the LCP element. Adding it to every image defeats the purpose and slows down the initial load.
Blur placeholders
Show a blurred preview while the full image loads:
// For static imports, the blur hash is generated automatically
import heroImg from '@/public/hero.jpg';
<Image
src={heroImg}
alt="Hero"
placeholder="blur"
/>
For dynamic images, provide a base64-encoded blur data URL:
<Image
src={product.imageUrl}
alt={product.name}
width={400}
height={300}
placeholder="blur"
blurDataURL={product.blurHash}
/>
Generate blur data URLs at upload time using a library like plaiceholder:
import { getPlaiceholder } from 'plaiceholder';
async function getBlurDataUrl(imageUrl: string) {
const res = await fetch(imageUrl);
const buffer = Buffer.from(await res.arrayBuffer());
const { base64 } = await getPlaiceholder(buffer, { size: 10 });
return base64;
}
Configuring external image domains
To use images from external URLs, configure allowed domains in next.config.ts:
// next.config.ts
import type { NextConfig } from 'next';
const config: NextConfig = {
images: {
remotePatterns: [
{
protocol: 'https',
hostname: 'images.unsplash.com',
},
{
protocol: 'https',
hostname: '*.amazonaws.com',
},
{
protocol: 'https',
hostname: 'cdn.example.com',
pathname: '/uploads/**',
},
],
},
};
export default config;
Use remotePatterns instead of the deprecated domains array. Patterns support wildcards and path restrictions for better security.
Custom image loaders
Replace the default Next.js image optimization with a third-party CDN:
// next.config.ts
const config: NextConfig = {
images: {
loader: 'custom',
loaderFile: './lib/image-loader.ts',
},
};
// lib/image-loader.ts
interface ImageLoaderParams {
src: string;
width: number;
quality?: number;
}
export default function cloudinaryLoader({
src,
width,
quality,
}: ImageLoaderParams): string {
const params = [
`f_auto`,
`c_limit`,
`w_${width}`,
`q_${quality || 'auto'}`,
];
return `https://res.cloudinary.com/your-cloud/image/upload/${params.join(',')}${src}`;
}
This redirects all next/image requests through Cloudinary’s transformation pipeline, giving you access to advanced features like face detection cropping and automatic format selection.
Art direction with the picture element
For cases where you need completely different images at different breakpoints (not just different sizes of the same image), use the native <picture> element:
export function ResponsiveHero() {
return (
<picture>
<source
media="(max-width: 640px)"
srcSet="/hero-mobile.webp"
type="image/webp"
/>
<source
media="(max-width: 1024px)"
srcSet="/hero-tablet.webp"
type="image/webp"
/>
<Image
src="/hero-desktop.jpg"
alt="Hero banner"
width={1920}
height={600}
priority
/>
</picture>
);
}
Performance configuration
Fine-tune the image optimization pipeline:
// next.config.ts
const config: NextConfig = {
images: {
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
qualities: [25, 50, 75],
},
};
- formats: AVIF is smaller than WebP but slower to encode. List it first so browsers that support it get the better format.
- deviceSizes: The widths used for
100vwimages. Remove sizes you do not need to reduce the number of generated variants. - imageSizes: Widths for images with explicit
sizesvalues smaller than the smallest device size. - minimumCacheTTL: How long optimized images are cached. Increase this for images that rarely change.
Common mistakes
Missing sizes prop. Without it, the browser downloads the largest image variant. This is the single most common performance issue with next/image.
Using fill without a sized parent. The image collapses to zero height or overflows because it has no reference for its dimensions.
Adding priority to too many images. Only the LCP image needs it. Preloading multiple images competes for bandwidth and slows everything down.
Not setting alt text. Beyond accessibility, missing alt text hurts SEO. Every image should have descriptive alt text or an empty alt="" for decorative images.
The next/image component handles most optimization automatically, but configuring sizes, priority, and placeholder correctly transforms a good setup into a great one. Measure your changes with Lighthouse and the Chrome DevTools network tab to verify that the right image sizes are being served at each breakpoint.
Related articles
- Next.js Next.js Streaming and Loading UI: Instant Pages
Build instant-loading Next.js pages with streaming, Suspense boundaries, loading.tsx files, and skeleton UIs for the best user experience.
- Next.js Next.js Intercepting Routes for Modals
Build modal patterns in Next.js using intercepting routes. Learn the (.) convention, combining with parallel routes, and handling hard refreshes.
- Next.js Next.js ISR and On-Demand Revalidation Explained
Master Incremental Static Regeneration and on-demand revalidation in Next.js to serve fresh content without rebuilding your entire site.
- Next.js Authentication Middleware Patterns in Next.js
Implement authentication middleware in Next.js to protect routes, redirect unauthenticated users, and manage JWT or session-based auth at the edge.