Next.js Image Optimization — next/image Complete Guide

Sanjeev SharmaSanjeev Sharma
5 min read

Advertisement

Introduction

The next/image component is one of Next.js's most impactful performance features. It automatically converts images to modern formats like WebP and AVIF, resizes them for each device, lazy loads offscreen images, and prevents Cumulative Layout Shift (CLS) by reserving space before the image loads. Using next/image instead of a plain img tag can reduce image payload by 60–80% and dramatically improve Core Web Vitals scores.

Why This Matters

Images are consistently the largest contributor to page weight — often 60–80% of total bytes transferred. A 3MB hero image served to a mobile device on a slow connection can cause a 5+ second LCP, immediately failing Google's Core Web Vitals threshold.

next/image solves this automatically: it serves WebP to Chrome (30% smaller than JPEG), AVIF to supported browsers (50% smaller than JPEG), resizes to the exact screen dimensions needed, and lazy loads everything below the fold. You get all this with a single component swap.

In 2025, LCP and CLS are both Google ranking signals. Proper image optimization is one of the highest-ROI SEO improvements you can make.

Basic Usage

import Image from 'next/image'
 
export default function ProductCard() {
  return (
    <Image
      src="/images/product.jpg"
      alt="Blue running shoes"
      width={800}
      height={600}
      className="rounded-lg"
    />
  )
}

Always provide a descriptive alt attribute — it improves accessibility and contributes to image SEO.

fill for Responsive Layout Containers

When you do not know the image dimensions ahead of time, use fill with a positioned parent:

import Image from 'next/image'
 
export function HeroBanner() {
  return (
    <div className="relative w-full h-96">
      <Image
        src="/images/hero.jpg"
        alt="Mountain landscape at sunrise"
        fill
        className="object-cover"
        priority
      />
    </div>
  )
}

The parent must have position: relative (or absolute/fixed). object-cover controls how the image fills the space.

priority for LCP Images

Mark your above-the-fold hero image with priority to disable lazy loading and add a preload link:

import Image from 'next/image'
 
export function Hero() {
  return (
    <Image
      src="/images/hero.webp"
      alt="Hero image"
      width={1200}
      height={630}
      priority
    />
  )
}

Only use priority on images visible on initial load. Overusing it defeats its purpose.

Responsive Images with sizes

The sizes prop tells the browser how wide the image will be at each breakpoint:

import Image from 'next/image'
 
export function BlogCard({ post }: { post: Post }) {
  return (
    <div className="w-full md:w-1/2 lg:w-1/3">
      <Image
        src={post.coverImage}
        alt={post.title}
        fill
        sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw"
        className="object-cover"
      />
    </div>
  )
}

Without sizes, Next.js defaults to 100vw, downloading a full-width image even on mobile.

Remote Images with remotePatterns

Configure allowed external domains in next.config.js:

// next.config.js
/** @type {import('next').NextConfig} */
const nextConfig = {
  images: {
    remotePatterns: [
      {
        protocol: 'https',
        hostname: 'images.unsplash.com',
        pathname: '/**',
      },
      {
        protocol: 'https',
        hostname: 'cdn.example.com',
        pathname: '/uploads/**',
      },
    ],
  },
}
 
module.exports = nextConfig

Then use the external URL in src directly:

<Image
  src="https://images.unsplash.com/photo-1234567890"
  alt="Photo from Unsplash"
  width={800}
  height={600}
/>

placeholder blur for Perceived Performance

Show a blurred placeholder while the image loads:

import Image from 'next/image'
import productImage from '@/public/images/product.jpg'
 
export function ProductImage() {
  return (
    <Image
      src={productImage}
      alt="Product"
      placeholder="blur"
      // blurDataURL auto-generated for static imports
    />
  )
}

For dynamic remote images, provide a base64-encoded blur data URL:

<Image
  src={product.imageUrl}
  alt={product.name}
  width={400}
  height={400}
  placeholder="blur"
  blurDataURL="data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAgGBgcGBQgHBwcJ..."
/>

Avatar and Circular Images

import Image from 'next/image'
 
export function Avatar({ user }: { user: { name: string; image: string } }) {
  return (
    <div className="relative w-12 h-12 rounded-full overflow-hidden">
      <Image
        src={user.image}
        alt={`${user.name} avatar`}
        fill
        className="object-cover"
        sizes="48px"
      />
    </div>
  )
}

Custom CDN Loader

Integrate with Cloudinary, Imgix, or any image CDN:

// lib/imageLoader.ts
export default function cloudinaryLoader({
  src,
  width,
  quality,
}: {
  src: string
  width: number
  quality?: number
}) {
  const params = ['f_auto', 'c_limit', `w_${width}`, `q_${quality ?? 75}`]
  return `https://res.cloudinary.com/demo/image/upload/${params.join(',')}/${src}`
}
// next.config.js
const nextConfig = {
  images: {
    loader: 'custom',
    loaderFile: './lib/imageLoader.ts',
  },
}

Common Mistakes

  • Using a plain img tag instead of next/image for content images — misses all optimizations
  • Omitting width and height without fill, causing layout shift (CLS)
  • Not setting priority on the LCP image — it lazy loads and hurts LCP score
  • Omitting sizes when using fill, causing mobile devices to download full-width images
  • Not configuring remotePatterns for external hosts — Next.js blocks them by default

Best Practices

  • Always use next/image for content images — never plain img
  • Set priority on exactly one image per page: the hero or first visible image
  • Provide accurate sizes values to reduce mobile download sizes by up to 3x
  • Use placeholder="blur" on large images to improve perceived loading performance
  • Use fill + object-cover for flexible containers instead of fixed width/height
  • Store static images in /public and configure remotePatterns for external URLs

Key Takeaways

  • next/image automatically converts to WebP/AVIF, reducing file size 30–50% vs JPEG
  • priority disables lazy loading and adds a preload link — use it only on the LCP image
  • fill requires a position: relative parent and must use sizes for efficiency
  • sizes tells the browser display width at each breakpoint — critical for responsive images
  • remotePatterns in next.config.js must whitelist every external image hostname
  • placeholder="blur" prevents layout shift and improves perceived performance for large images
  • quality defaults to 75 — increasing it improves sharpness at the cost of file size
  • Custom loaders enable integration with Cloudinary, Imgix, Contentful, and other CDNs

Advertisement

Sanjeev Sharma

Written by

Sanjeev Sharma

Full Stack Engineer · E-mopro

Related reading