DEV Community

Cover image for next/image Deep Dive — Optimization, Lazy Loading, and the Props That Actually Matter
Aon infotech
Aon infotech

Posted on

next/image Deep Dive — Optimization, Lazy Loading, and the Props That Actually Matter

next/image does a lot automatically — format conversion, lazy loading, preventing layout shift. But several props change behavior significantly, and getting them wrong either hurts performance or breaks things in production. Here's the complete picture of what actually matters, with the approach used in the image-heavy generation results interface.


What next/image Does By Default

When you use <Image> from next/image, you get automatically:

  • Format conversion: Serves WebP or AVIF to browsers that support them
  • Lazy loading: Images below the fold don't load until the user scrolls near them
  • Size optimization: Resizes images to the dimensions you specify
  • Layout stability: Reserves space before image loads, preventing cumulative layout shift (CLS) These happen without any configuration. The props you add tune this behavior.

The sizes Prop — The Most Underused Optimization

The sizes prop tells the browser what size the image will be at different viewport widths. Without it, Next.js can't serve optimally-sized images and defaults to generating several sizes that may be larger than needed.

// Without sizes — Next.js doesn't know the display size
<Image src="/hero.jpg" alt="Hero" fill />

// With sizes — Next.js serves the right size for each viewport
<Image
  src="/hero.jpg"
  alt="Hero"
  fill
  sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw"
/>
Enter fullscreen mode Exit fullscreen mode

Read the sizes value as: "On mobile (under 768px), this image takes 100% of the viewport width. On tablets (under 1200px), 50%. On desktop, 33%."

Next.js uses this to calculate which image size to serve. Correct sizes values can reduce image payload by 50-70% on mobile.

Common patterns:

// Full-width hero or banner
sizes="100vw"

// Half-width on desktop, full on mobile
sizes="(max-width: 768px) 100vw, 50vw"

// Card grid — 1 col mobile, 2 col tablet, 3 col desktop
sizes="(max-width: 640px) 100vw, (max-width: 1024px) 50vw, 33vw"

// Small thumbnail — always small
sizes="150px"
Enter fullscreen mode Exit fullscreen mode

priority — For Above-the-Fold Images

By default, next/image lazy loads everything. For images visible immediately on page load (hero images, header images, above-the-fold content), lazy loading is counterproductive — you want these to load as fast as possible.

// Hero image — load immediately, don't lazy load
<Image
  src="/hero.jpg"
  alt="Page hero"
  width={1200}
  height={600}
  priority
/>
Enter fullscreen mode Exit fullscreen mode

Add priority to the first image visible on page load. This tells Next.js to preload it and not apply lazy loading.

The rule: One or two images maximum per page should have priority. If you add it to many images, you've eliminated the performance benefit of lazy loading.


fill vs Width/Height — When to Use Each

Explicit width/height: Use when you know the exact dimensions at render time. Generates precise sizes.

<Image
  src="/thumbnail.jpg"
  alt="Product thumbnail"
  width={300}
  height={200}
/>
Enter fullscreen mode Exit fullscreen mode

fill mode: Use when the image should fill its container. The container must be position: relative (or absolute or fixed).

<div className="relative h-64 w-full">
  <Image
    src="/cover.jpg"
    alt="Article cover"
    fill
    sizes="100vw"
    className="object-cover"
  />
</div>
Enter fullscreen mode Exit fullscreen mode

className="object-cover" on the Image determines how it fills the container. object-cover maintains aspect ratio and crops. object-contain maintains aspect ratio with letterboxing.


quality — When Default Isn't Right

Default quality is 75, which is a good balance. For specific cases you might adjust:

// Photographic images — default 75 is fine
<Image src="/photo.jpg" alt="Photo" width={800} height={600} />

// Icons or images with sharp lines — higher quality prevents artifacts
<Image src="/logo.png" alt="Logo" width={100} height={50} quality={90} />

// Background textures where quality difference isn't visible
<Image src="/texture.jpg" alt="" width={1200} height={800} quality={60} />
Enter fullscreen mode Exit fullscreen mode

Higher quality = larger file size. The default is right for most cases.


External Images — The remotePatterns Config

By default, next/image only optimizes images from your own domain. For external images, you need to configure allowed patterns:

// next.config.js
/** @type {import('next').NextConfig} */
const nextConfig = {
  images: {
    remotePatterns: [
      {
        protocol: 'https',
        hostname: 'cdn.example.com',
        port: '',
        pathname: '/images/**',
      },
      {
        protocol: 'https',
        hostname: '*.cdn-example.com', // wildcard subdomain
      },
    ],
  },
};

module.exports = nextConfig;
Enter fullscreen mode Exit fullscreen mode

Be specific with pathname patterns. Wildcard /** allows any path on that host. More restrictive patterns are safer.


The placeholder Prop — Blur-Up Loading

For a smooth loading experience, next/image supports a blur placeholder that shows while the full image loads:

// Static images — Next.js generates blurDataURL automatically at build time
import heroImage from '/public/hero.jpg';

<Image
  src={heroImage}
  alt="Hero"
  placeholder="blur"
  priority
/>
Enter fullscreen mode Exit fullscreen mode

For dynamic images (external URLs), you need to provide the blur data yourself:

// Generate a tiny blurred placeholder (many services provide these)
const blurDataURL = 'data:image/jpeg;base64,/9j/4AAQ...'; // tiny base64 image

<Image
  src={dynamicImageUrl}
  alt="Dynamic image"
  width={800}
  height={600}
  placeholder="blur"
  blurDataURL={blurDataURL}
/>
Enter fullscreen mode Exit fullscreen mode

The placeholder="blur" approach significantly improves perceived loading performance for above-the-fold images.


Checking What's Actually Being Served

To verify your image optimization is working:

  1. Open DevTools → Network tab
  2. Filter by Img
  3. Check the Type column — should show webp or avif instead of jpeg/png
  4. Check image size in the Size column vs original file size A well-optimized image should be significantly smaller than the original and in a modern format.

Common Mistakes

Forgetting sizes on fill images. Without sizes, Next.js can't optimize for viewport — generates full-size images for all viewports.

Adding priority to too many images. It's for above-the-fold only. One or two per page maximum.

Not configuring remotePatterns. External images without pattern configuration fall back to unoptimized serving, breaking the performance benefits.

Using <img> instead of <Image>. Native <img> tags don't get any next/image optimization — no format conversion, no lazy loading, no layout stability. ESLint rule next/no-img-element catches this.


Summary Table

Prop When to use
sizes Always, when using fill or when image width changes across viewports
priority First 1-2 visible images on page load only
fill When image should fill a container with unknown dimensions
quality When default 75 is visibly too low (icons) or unnecessarily high
placeholder="blur" Above-the-fold images where loading is visible to users

The three that move the needle most: correct sizes (biggest file size impact), priority on LCP images (biggest perceived performance impact), and remotePatterns configuration (required for external images to optimize at all).


Loader Configuration for Custom CDNs

If you serve images from a custom CDN with its own optimization parameters, you can configure a custom loader:

// next.config.js
module.exports = {
  images: {
    loader: 'custom',
    loaderFile: './imageLoader.js',
  },
};
Enter fullscreen mode Exit fullscreen mode
// imageLoader.js
export default function myImageLoader({ src, width, quality }) {
  return `https://your-cdn.com/image?url=${src}&w=${width}&q=${quality || 75}`;
}
Enter fullscreen mode Exit fullscreen mode

This gives you full control over how image URLs are constructed for optimization, useful when your CDN uses different query parameter formats than Next.js expects.

For most applications using standard hosting, the default loader is correct. Custom loaders are for specific CDN integrations that need URL format control.


Image Domains Migration From Old Config

If you're on an older Next.js version using domains config, migrate to remotePatterns:

// Old (deprecated)
images: {
  domains: ['cdn.example.com'],
}

// New (use this)
images: {
  remotePatterns: [
    { protocol: 'https', hostname: 'cdn.example.com' }
  ],
}
Enter fullscreen mode Exit fullscreen mode

remotePatterns is more secure — it supports pathname restrictions that domains doesn't, preventing a compromised external hostname from serving arbitrary paths through your Next.js image optimizer.

Top comments (0)