Image Optimization
Serve optimized, responsive images with next/image -- automatic format conversion, lazy loading, and size hints.
Recipe
Quick-reference recipe card -- copy-paste ready.
import Image from "next/image";
// Local image (auto width/height from import)
import heroImage from "@/public/hero.jpg";
<Image src={heroImage} alt="Hero banner" priority />
// Remote image (must specify width and height)
<Image
src="https://cdn.example.com/photo.jpg"
alt="Product photo"
width={800}
height={600}
/>
// Fill container (responsive, no explicit dimensions)
<div className="relative h-64 w-full">
<Image
src="/banner.jpg"
alt="Banner"
fill
className="object-cover"
sizes="100vw"
/>
</div>When to reach for this: Any time you render an image. next/image handles lazy loading, format conversion (WebP/AVIF), responsive sizing, and CLS prevention out of the box.
Working Example
// app/gallery/page.tsx
import Image from "next/image";
type Photo = {
id: string;
url: string;
alt: string;
width: number;
height: number;
};
async function getPhotos(): Promise<Photo[]> {
const res = await fetch("https://api.example.com/photos", {
next: { revalidate: 3600 },
});
return res.json();
}
export default async function GalleryPage() {
const photos = await getPhotos();
return (
<main className="max-w-6xl mx-auto p-6">
<h1 className="text-3xl font-bold mb-8">Gallery</h1>
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4">
{photos.map((photo, index) => (
<div key={photo.id} className="relative aspect-[4/3]">
<Image
src={photo.url}
alt={photo.alt}
fill
sizes="(max-width: 640px) 100vw, (max-width: 1024px) 50vw, 33vw"
className="object-cover rounded-lg"
priority={index < 3} // prioritize above-the-fold images
/>
</div>
))}
</div>
</main>
);
}// app/components/product-card.tsx
import Image from "next/image";
import Link from "next/link";
type Product = {
slug: string;
name: string;
price: number;
imageUrl: string;
};
export function ProductCard({ product }: { product: Product }) {
return (
<Link
href={`/products/${product.slug}`}
className="group block border rounded-lg overflow-hidden"
>
<div className="relative aspect-square bg-gray-50">
<Image
src={product.imageUrl}
alt={product.name}
fill
sizes="(max-width: 640px) 50vw, (max-width: 1024px) 33vw, 25vw"
className="object-contain group-hover:scale-105 transition-transform"
/>
</div>
<div className="p-4">
<h3 className="font-medium">{product.name}</h3>
<p className="text-gray-600">${product.price.toFixed(2)}</p>
</div>
</Link>
);
}// next.config.ts -- configure remote image domains
import type { NextConfig } from "next";
const config: NextConfig = {
images: {
remotePatterns: [
{
protocol: "https",
hostname: "cdn.example.com",
pathname: "/images/**",
},
{
protocol: "https",
hostname: "*.unsplash.com",
},
],
formats: ["image/avif", "image/webp"], // prefer AVIF, fallback to WebP
},
};
export default config;What this demonstrates:
fillmode for responsive images in a grid layout- The
sizesprop telling the browser how wide the image will be at different viewport sizes priorityfor above-the-fold images (disables lazy loading, adds preload hint)- Remote image pattern configuration in
next.config.ts aspect-[4/3]andaspect-squarecontainers to prevent CLS
Deep Dive
How It Works
next/imagerenders an<img>tag withsrcsetandsizesattributes. The browser selects the best image size based on the viewport and device pixel ratio.- Images are optimized on-demand at request time (not build time). The optimized version is cached on the server and CDN for subsequent requests.
- By default, images are lazy loaded -- they only fetch when they enter the viewport. The
priorityprop disables lazy loading and adds a<link rel="preload">for LCP images. - The
fillprop makes the image fill its parent container (the parent must beposition: relative,absolute, orfixed). Use this when you do not know the image dimensions at build time. - Next.js automatically serves images in modern formats (WebP, AVIF) to browsers that support them, with fallback to the original format.
- The
widthandheightprops set the intrinsic aspect ratio to prevent Cumulative Layout Shift (CLS). They do not set the rendered size -- use CSS for that.
Variations
Responsive image with explicit breakpoints:
<Image
src="/hero.jpg"
alt="Hero"
width={1200}
height={600}
sizes="(max-width: 768px) 100vw, (max-width: 1200px) 80vw, 1200px"
className="w-full h-auto"
/>Placeholder blur (local images):
import heroImg from "@/public/hero.jpg";
<Image
src={heroImg}
alt="Hero"
placeholder="blur" // auto-generates blurDataURL for local images
/>Placeholder blur (remote images):
<Image
src="https://cdn.example.com/photo.jpg"
alt="Photo"
width={800}
height={600}
placeholder="blur"
blurDataURL="data:image/jpeg;base64,/9j/4AAQ..." // must provide manually
/>Disabling optimization (SVGs, animated GIFs):
<Image
src="/logo.svg"
alt="Logo"
width={200}
height={50}
unoptimized // serve the original file as-is
/>TypeScript Notes
import Image, { type ImageProps } from "next/image";
// Extending Image props
type AvatarProps = Omit<ImageProps, "alt"> & {
name: string;
};
function Avatar({ name, ...props }: AvatarProps) {
return (
<Image
alt={`Avatar for ${name}`}
className="rounded-full"
{...props}
/>
);
}
// StaticImageData type for imported images
import type { StaticImageData } from "next/image";
import fallback from "@/public/fallback.jpg";
function getImage(url?: string): string | StaticImageData {
return url ?? fallback;
}Gotchas
-
Missing
sizesprop withfill-- Withoutsizes, the browser assumes the image is100vwwide, downloading an unnecessarily large file. Fix: Always provide asizesprop that matches your CSS layout. -
Remote images require
remotePatterns-- Using a remote URL without configuringremotePatternsinnext.config.tsthrows a build error. Fix: Add the hostname (and optionally pathname pattern) toimages.remotePatterns. -
fillrequires a positioned parent -- If the parent element does not haveposition: relative(orabsolute/fixed), the image breaks out of its container. Fix: AddclassName="relative"to the parent. -
priorityon too many images -- Marking many images asprioritydefeats the purpose and slows down the page. Fix: Only usepriorityon the LCP image (usually 1-2 images above the fold). -
Width and height do not control rendered size --
width={800} height={600}sets the intrinsic aspect ratio, not the displayed size. The image may render smaller or larger depending on CSS. Fix: Use CSS classes (className) to control the rendered dimensions. -
AVIF encoding is slow -- AVIF produces smaller files but takes longer to generate on the first request. Fix: Accept the cold-start latency, or remove
"image/avif"fromimages.formatsif response time is critical.
Alternatives
| Alternative | Use When | Don't Use When |
|---|---|---|
next/image | All images in a Next.js app (the default choice) | You need raw <img> for a specific reason |
Native <img> tag | Simple static images with no optimization needs | You want automatic WebP/AVIF, lazy loading, and srcset |
<picture> element | You need art direction (different crops at different sizes) | Responsive sizing alone is sufficient |
| Cloudinary or Imgix | You need advanced transformations (crop, watermark, face detection) | Built-in Next.js optimization is enough |
| SVG inline | Icons and illustrations that need to be styled with CSS | Photographic content |
FAQs
What does next/image do that a regular img tag does not?
- Automatic format conversion (WebP/AVIF)
- Lazy loading by default
- Responsive
srcsetandsizesgeneration - Cumulative Layout Shift (CLS) prevention via width/height or fill
- On-demand optimization and CDN caching
When should I use the fill prop versus explicit width and height?
- Use
fillwhen you do not know the image dimensions at build time (e.g., user-uploaded images). The image fills its parent container. - Use explicit
widthandheightwhen dimensions are known. These set the intrinsic aspect ratio, not the rendered size.
What does the priority prop do?
- Disables lazy loading for the image.
- Adds a
<link rel="preload">hint so the browser fetches it immediately. - Use it only for the LCP (Largest Contentful Paint) image, typically 1-2 images above the fold.
Gotcha: I set width={800} and height={600} but the image renders at a different size. Why?
width and height set the intrinsic aspect ratio for CLS prevention, not the rendered size. Use CSS classes (className) to control the actual display dimensions.
Gotcha: I used fill but the image breaks out of its container. What went wrong?
The parent element must have position: relative, absolute, or fixed. Add className="relative" to the parent <div>.
Why do I need a sizes prop when using fill?
Without sizes, the browser assumes the image is 100vw wide and downloads an unnecessarily large file. Provide sizes to match your CSS layout:
sizes="(max-width: 640px) 100vw, (max-width: 1024px) 50vw, 33vw"How do I configure remote image domains?
Add remotePatterns to next.config.ts:
images: {
remotePatterns: [
{ protocol: "https", hostname: "cdn.example.com", pathname: "/images/**" },
],
}How do I add a blur placeholder for remote images?
For remote images, you must provide blurDataURL manually (a base64-encoded tiny image). Local images get automatic blur generation with placeholder="blur".
How do I type a component that extends ImageProps in TypeScript?
import Image, { type ImageProps } from "next/image";
type AvatarProps = Omit<ImageProps, "alt"> & {
name: string;
};
function Avatar({ name, ...props }: AvatarProps) {
return <Image alt={`Avatar for ${name}`} {...props} />;
}What is the StaticImageData type and when do I use it?
StaticImageData is the type returned when importing a local image file. Use it when a function or prop can accept either a local import or a URL string:
import type { StaticImageData } from "next/image";
function getImage(url?: string): string | StaticImageData {
return url ?? fallbackImage;
}When should I use the unoptimized prop?
Use unoptimized for images that should not be processed, such as SVGs or animated GIFs. The file is served as-is without format conversion or resizing.
Is AVIF always better than WebP?
AVIF produces smaller files but encoding is slower on the first request (cold start). If response time is critical, consider removing "image/avif" from images.formats and using WebP only.
Related
- Font Optimization -- Optimizing web fonts alongside images
- Server Components -- Server-rendered pages with optimized images
- Static vs Dynamic -- How image-heavy pages are statically generated
- Streaming -- Progressive loading of image-rich pages