React SME Cookbook
All FAQs

Search Documentation

Search across all documentation pages

next-imageimage-optimizationresponsiveblur-placeholderLCP

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/image automatically 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 priority prop disables lazy loading and preloads the image, making it ideal for above-the-fold LCP (Largest Contentful Paint) images.
  • fill mode makes the image fill its parent container. The parent must have position: relative (or absolute or fixed) and defined dimensions.
  • The sizes prop 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 provide width, height, and blurDataURL.
  • Remote images require width and height props (or fill) 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/image exports ImageProps and StaticImageData types.
  • Static imports return StaticImageData with src, width, height, and blurDataURL properties.
  • The loader function is typed as ImageLoader from next/image.
import type { ImageProps, StaticImageData } from "next/image";
 
interface HeroProps {
  image: StaticImageData;
  alt: string;
  priority?: boolean;
}

Gotchas

  • Using fill without sizes forces the browser to download the largest image variant. Always pair fill with an appropriate sizes prop.
  • The parent of a fill image must have position: relative and defined dimensions. Without this, the image collapses to zero height.
  • placeholder="blur" with remote images requires a blurDataURL prop. Only static imports generate this automatically.
  • The priority prop should only be used on above-the-fold images (typically one or two per page). Overusing it defeats lazy loading benefits.
  • remotePatterns in next.config requires a server restart to take effect. Changes are not picked up by hot reload.
  • Setting width and height does 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 remotePatterns will fail with a 400 error at runtime, not at build time.

Alternatives

ApproachProsCons
next/imageBuilt-in optimization, lazy loading, format conversionComplex props, server-side dependency
Native img tagSimple, no configurationNo optimization, no lazy loading by default
Cloudinary or ImgixAdvanced transformations, CDN deliveryExternal service, additional cost
@unpic/reactFramework-agnostic, works with multiple CDNsNo 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 Accept header.
  • 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 priority only 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 fill with an appropriate sizes prop 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 (or absolute or fixed).
  • 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, and blurDataURL.
  • Remote images require explicit width and height props (or fill) since Next.js cannot inspect them at build time.
  • placeholder="blur" works automatically with static imports but needs a manual blurDataURL for 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";
  • StaticImageData is the return type of static imports, with src, width, height, and blurDataURL.
  • ImageLoader types the custom loader function.
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 remotePatterns in next.config.ts and restart the server.