React SME Cookbook
All FAQs

Search Documentation

Search across all documentation pages

next-imageoptimizationlazy-loadingresponsivewebpavif

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:

  • fill mode for responsive images in a grid layout
  • The sizes prop telling the browser how wide the image will be at different viewport sizes
  • priority for above-the-fold images (disables lazy loading, adds preload hint)
  • Remote image pattern configuration in next.config.ts
  • aspect-[4/3] and aspect-square containers to prevent CLS

Deep Dive

How It Works

  • next/image renders an <img> tag with srcset and sizes attributes. 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 priority prop disables lazy loading and adds a <link rel="preload"> for LCP images.
  • The fill prop makes the image fill its parent container (the parent must be position: relative, absolute, or fixed). 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 width and height props 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 sizes prop with fill -- Without sizes, the browser assumes the image is 100vw wide, downloading an unnecessarily large file. Fix: Always provide a sizes prop that matches your CSS layout.

  • Remote images require remotePatterns -- Using a remote URL without configuring remotePatterns in next.config.ts throws a build error. Fix: Add the hostname (and optionally pathname pattern) to images.remotePatterns.

  • fill requires a positioned parent -- If the parent element does not have position: relative (or absolute/fixed), the image breaks out of its container. Fix: Add className="relative" to the parent.

  • priority on too many images -- Marking many images as priority defeats the purpose and slows down the page. Fix: Only use priority on 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" from images.formats if response time is critical.

Alternatives

AlternativeUse WhenDon't Use When
next/imageAll images in a Next.js app (the default choice)You need raw <img> for a specific reason
Native <img> tagSimple static images with no optimization needsYou want automatic WebP/AVIF, lazy loading, and srcset
<picture> elementYou need art direction (different crops at different sizes)Responsive sizing alone is sufficient
Cloudinary or ImgixYou need advanced transformations (crop, watermark, face detection)Built-in Next.js optimization is enough
SVG inlineIcons and illustrations that need to be styled with CSSPhotographic content

FAQs

What does next/image do that a regular img tag does not?
  • Automatic format conversion (WebP/AVIF)
  • Lazy loading by default
  • Responsive srcset and sizes generation
  • 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 fill when you do not know the image dimensions at build time (e.g., user-uploaded images). The image fills its parent container.
  • Use explicit width and height when 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.