Next.js Image Optimization
Recipe
Use the next/image component to automatically optimize images with lazy loading, responsive sizing, and modern formats (WebP/AVIF). Configure remote patterns for external image sources.
// app/components/optimized-image.tsx
import Image from "next/image";
// Local image (static import enables automatic blur placeholder)
import heroPhoto from "@/public/images/hero.jpg";
export function HeroImage() {
return (
<Image
src={heroPhoto}
alt="A scenic mountain landscape"
placeholder="blur"
priority
className="rounded-lg"
/>
);
}// Remote image with explicit dimensions
export function RemoteImage() {
return (
<Image
src="https://images.unsplash.com/photo-example"
alt="Unsplash photo"
width={800}
height={600}
className="rounded-lg"
/>
);
}// next.config.ts
import type { NextConfig } from "next";
const nextConfig: NextConfig = {
images: {
remotePatterns: [
{
protocol: "https",
hostname: "images.unsplash.com",
},
{
protocol: "https",
hostname: "cdn.pixabay.com",
},
],
},
};
export default nextConfig;Working Example
A responsive image gallery with blur placeholders and a lightbox-ready layout:
// app/components/image-gallery.tsx
import Image from "next/image";
interface GalleryImage {
src: string;
alt: string;
blurDataURL: string;
width: number;
height: number;
}
const images: GalleryImage[] = [
{
src: "https://images.unsplash.com/photo-1506744038136-46273834b3fb",
alt: "Mountain lake at sunrise",
blurDataURL: "data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAMCA...",
width: 1200,
height: 800,
},
{
src: "https://images.unsplash.com/photo-1469474968028-56623f02e42e",
alt: "Forest path in autumn",
blurDataURL: "data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAMCA...",
width: 1200,
height: 800,
},
{
src: "https://images.unsplash.com/photo-1447752875215-b2761acb3c5d",
alt: "Misty mountain range",
blurDataURL: "data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAMCA...",
width: 1200,
height: 800,
},
];
export function ImageGallery() {
return (
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3">
{images.map((image, index) => (
<div key={image.src} className="relative aspect-[3/2] overflow-hidden rounded-xl">
<Image
src={image.src}
alt={image.alt}
fill
sizes="(max-width: 640px) 100vw, (max-width: 1024px) 50vw, 33vw"
placeholder="blur"
blurDataURL={image.blurDataURL}
priority={index === 0}
className="object-cover transition-transform duration-300 hover:scale-105"
/>
</div>
))}
</div>
);
}// app/gallery/page.tsx
import { ImageGallery } from "@/app/components/image-gallery";
export default function GalleryPage() {
return (
<main className="mx-auto max-w-6xl px-4 py-8">
<h1 className="mb-6 text-3xl font-bold">Photo Gallery</h1>
<ImageGallery />
</main>
);
}Deep Dive
How It Works
next/imageautomatically serves images in modern formats (WebP, AVIF) based on browser support via the built-in Image Optimization API.- Images are lazy-loaded by default. Only images visible in the viewport (or close to it) are fetched.
- The
priorityprop disables lazy loading and preloads the image, making it ideal for above-the-fold LCP (Largest Contentful Paint) images. fillmode makes the image fill its parent container. The parent must haveposition: relative(orabsoluteorfixed) and defined dimensions.- The
sizesprop tells the browser which image width to request at each viewport breakpoint. Without it, Next.js serves the full-size image to all devices. - Static imports (e.g.,
import hero from "@/public/hero.jpg") automatically providewidth,height, andblurDataURL. - Remote images require
widthandheightprops (orfill) because Next.js cannot inspect them at build time.
Variations
Fill mode with aspect ratio container:
<div className="relative aspect-video w-full">
<Image
src="/images/banner.jpg"
alt="Banner"
fill
sizes="100vw"
className="object-cover"
/>
</div>Responsive sizes for different layouts:
// Full-width hero
<Image src={src} alt={alt} fill sizes="100vw" />
// Two-column grid
<Image src={src} alt={alt} fill sizes="(max-width: 768px) 100vw, 50vw" />
// Three-column with sidebar
<Image src={src} alt={alt} fill sizes="(max-width: 640px) 100vw, (max-width: 1024px) 50vw, 33vw" />Custom loader for external CDN:
import Image from "next/image";
const cloudinaryLoader = ({ src, width, quality }: { src: string; width: number; quality?: number }) => {
return `https://res.cloudinary.com/demo/image/upload/w_${width},q_${quality || 75}/${src}`;
};
export function CloudinaryImage() {
return (
<Image
loader={cloudinaryLoader}
src="sample.jpg"
alt="Cloudinary image"
width={800}
height={600}
/>
);
}TypeScript Notes
next/imageexportsImagePropsandStaticImageDatatypes.- Static imports return
StaticImageDatawithsrc,width,height, andblurDataURLproperties. - The
loaderfunction is typed asImageLoaderfromnext/image.
import type { ImageProps, StaticImageData } from "next/image";
interface HeroProps {
image: StaticImageData;
alt: string;
priority?: boolean;
}Gotchas
- Using
fillwithoutsizesforces the browser to download the largest image variant. Always pairfillwith an appropriatesizesprop. - The parent of a
fillimage must haveposition: relativeand defined dimensions. Without this, the image collapses to zero height. placeholder="blur"with remote images requires ablurDataURLprop. Only static imports generate this automatically.- The
priorityprop should only be used on above-the-fold images (typically one or two per page). Overusing it defeats lazy loading benefits. remotePatternsinnext.configrequires a server restart to take effect. Changes are not picked up by hot reload.- Setting
widthandheightdoes not crop or resize the image element in the DOM. These values define the aspect ratio and request size. Use CSS for visual sizing. - Images from domains not listed in
remotePatternswill fail with a 400 error at runtime, not at build time.
Alternatives
| Approach | Pros | Cons |
|---|---|---|
| next/image | Built-in optimization, lazy loading, format conversion | Complex props, server-side dependency |
| Native img tag | Simple, no configuration | No optimization, no lazy loading by default |
| Cloudinary or Imgix | Advanced transformations, CDN delivery | External service, additional cost |
| @unpic/react | Framework-agnostic, works with multiple CDNs | No built-in optimization server |
FAQs
What formats does next/image automatically serve, and how does it decide?
- It serves WebP or AVIF based on the browser's
Acceptheader. - The built-in Image Optimization API handles format conversion automatically.
- No configuration is needed; it works out of the box.
When should you use the priority prop on an image?
- Use
priorityonly on above-the-fold LCP (Largest Contentful Paint) images. - Typically one or two images per page.
- It disables lazy loading and preloads the image.
- Overusing it defeats the lazy loading benefits.
Gotcha: What happens if you use fill without a sizes prop?
- The browser downloads the largest image variant for all devices.
- Always pair
fillwith an appropriatesizesprop to serve correctly sized images per breakpoint.
What CSS requirement does a parent element need for fill mode images?
- The parent must have
position: relative(orabsoluteorfixed). - It must also have defined dimensions (width/height or aspect ratio).
- Without this, the image collapses to zero height.
How do static imports differ from remote images in next/image?
- Static imports automatically provide
width,height, andblurDataURL. - Remote images require explicit
widthandheightprops (orfill) since Next.js cannot inspect them at build time. placeholder="blur"works automatically with static imports but needs a manualblurDataURLfor remote images.
How do you configure next/image to allow images from an external domain?
// next.config.ts
const nextConfig: NextConfig = {
images: {
remotePatterns: [
{ protocol: "https", hostname: "images.unsplash.com" },
],
},
};A server restart is required after changes.
Gotcha: Do width and height props crop or resize the image in the DOM?
- No. They define the aspect ratio and the requested image size from the optimization server.
- Use CSS classes for visual sizing in the browser.
How do you use a custom loader for an external CDN like Cloudinary?
const cloudinaryLoader = ({ src, width, quality }: { src: string; width: number; quality?: number }) => {
return `https://res.cloudinary.com/demo/image/upload/w_${width},q_${quality || 75}/${src}`;
};
<Image loader={cloudinaryLoader} src="sample.jpg" alt="Photo" width={800} height={600} />What TypeScript types does next/image export for typing image props?
import type { ImageProps, StaticImageData } from "next/image";StaticImageDatais the return type of static imports, withsrc,width,height, andblurDataURL.ImageLoadertypes the customloaderfunction.
What is the correct sizes value for a three-column grid with a sidebar?
sizes="(max-width: 640px) 100vw, (max-width: 1024px) 50vw, 33vw"This tells the browser to request full-width images on mobile, half on tablet, and one-third on desktop.
What error do you get for images from domains not in remotePatterns?
- A 400 error at runtime, not at build time.
- The image simply fails to load when the page is visited.
- Add the domain to
remotePatternsinnext.config.tsand restart the server.