Next.js

Image Optimization in Next.js

18 min Lesson 13 of 40

Introduction to Image Optimization

Images often account for the largest portion of a web page's size and can significantly impact loading performance. Next.js provides a powerful Image component that automatically optimizes images on-demand, reducing file sizes and improving Core Web Vitals scores. The next/image component handles lazy loading, responsive images, modern formats, and more.

Note: The Next.js Image component requires either Sharp (for Node.js runtime) or the built-in image optimization (for Edge Runtime). Sharp is automatically installed when you run npm install, but you may need to install it manually in some environments.

Basic Image Component Usage

Import and use the Image component from next/image:

// app/page.tsx import Image from 'next/image'; export default function Page() { return ( <div> <h1>My Image</h1> <Image src="/images/hero.jpg" alt="Hero image" width={800} height={600} /> </div> ); }

The Image component requires three essential props: src, alt, and either width/height or fill. These help Next.js properly optimize and display the image.

Local Images

For local images in the public directory or imported directly:

// Option 1: Public directory <Image src="/images/profile.jpg" alt="Profile picture" width={400} height={400} /> // Option 2: Import (automatically gets width/height) import profilePic from './profile.jpg'; <Image src={profilePic} alt="Profile picture" // width and height are automatically provided />

Remote Images

For images hosted on external domains, configure allowed domains in next.config.js:

// next.config.js module.exports = { images: { remotePatterns: [ { protocol: 'https', hostname: 'images.unsplash.com', port: '', pathname: '/photo-**', }, { protocol: 'https', hostname: 'cdn.example.com', }, ], }, };
// app/page.tsx <Image src="https://images.unsplash.com/photo-1234567890" alt="Unsplash photo" width={800} height={600} />
Warning: Always configure remotePatterns for external images to prevent unauthorized domains from using your image optimization service. This protects your server resources and prevents potential abuse.

Image Sizing Strategies

Next.js Image component supports different sizing strategies:

Fixed Size (width and height)

<Image src="/images/logo.png" alt="Logo" width={200} height={100} />

Responsive Size (sizes prop)

Use the sizes prop to specify how much space the image takes at different viewport widths:

<Image src="/images/hero.jpg" alt="Hero image" width={1200} height={800} sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw" />

This tells the browser:

  • On screens up to 768px: image is 100% of viewport width
  • On screens 769px to 1200px: image is 50% of viewport width
  • On screens wider than 1200px: image is 33% of viewport width

Fill Container (fill prop)

Use fill when you don't know the image dimensions or want the image to fill its container:

// CSS .image-container { position: relative; width: 100%; height: 400px; } // Component <div className="image-container"> <Image src="/images/banner.jpg" alt="Banner" fill style={{ objectFit: 'cover' }} /> </div>
Tip: When using fill, the parent element must have position: relative, position: fixed, or position: absolute. The Image component will automatically fill the entire parent container.

Image Loading Strategies

Control when and how images load:

Lazy Loading (Default)

Images load as they approach the viewport:

<Image src="/images/product.jpg" alt="Product" width={600} height={400} loading="lazy" // Default behavior />

Eager Loading

Load images immediately (use for above-the-fold content):

<Image src="/images/hero.jpg" alt="Hero" width={1200} height={600} loading="eager" priority // Equivalent to loading="eager" />

Priority Images

Mark critical images (like hero images) as priority to preload them:

<Image src="/images/hero.jpg" alt="Hero" width={1200} height={600} priority />
Note: Use priority sparingly and only for images visible above the fold. Too many priority images can actually harm performance by competing for bandwidth.

Placeholder Strategies

Show placeholders while images load:

Blur Placeholder (Automatic)

For imported images, Next.js automatically generates blur placeholders:

import heroImage from './hero.jpg'; <Image src={heroImage} alt="Hero" placeholder="blur" // blurDataURL is automatically generated />

Custom Blur Placeholder

Provide your own base64-encoded placeholder:

<Image src="/images/photo.jpg" alt="Photo" width={800} height={600} placeholder="blur" blurDataURL="data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD..." />

Empty Placeholder

<Image src="/images/photo.jpg" alt="Photo" width={800} height={600} placeholder="empty" />

Image Quality and Formats

Control image quality and format optimization:

Quality Setting

<Image src="/images/photo.jpg" alt="Photo" width={800} height={600} quality={75} // Default is 75, range is 1-100 /> // High quality for important images <Image src="/images/product.jpg" alt="Product detail" width={1000} height={1000} quality={90} />

Format Optimization

Next.js automatically serves modern formats (WebP, AVIF) when supported by the browser. Configure in next.config.js:

// next.config.js module.exports = { images: { formats: ['image/avif', 'image/webp'], // Default }, };

Responsive Images

Create responsive images that adapt to different screen sizes:

Complete Responsive Example

<Image src="/images/hero.jpg" alt="Hero image" width={1200} height={675} sizes="(max-width: 640px) 100vw, (max-width: 1024px) 80vw, (max-width: 1280px) 70vw, 60vw" style={{ width: '100%', height: 'auto', }} priority />

Responsive with Fill

// CSS Module .heroContainer { position: relative; width: 100%; height: 60vh; min-height: 400px; } @media (max-width: 768px) { .heroContainer { height: 40vh; min-height: 300px; } } // Component <div className={styles.heroContainer}> <Image src="/images/hero.jpg" alt="Hero" fill sizes="100vw" style={{ objectFit: 'cover' }} priority /> </div>

Image Component Props Reference

Complete list of important Image component props:

<Image // Required props src="/images/photo.jpg" // string or StaticImageData alt="Description" // string (important for accessibility) // Size props (choose one approach) width={800} // number - explicit width height={600} // number - explicit height fill // boolean - fill parent container // Loading props loading="lazy" // "lazy" | "eager" priority // boolean - preload image // Placeholder props placeholder="blur" // "blur" | "empty" blurDataURL="data:..." // string - base64 encoded image // Quality and optimization quality={75} // number (1-100) sizes="100vw" // string - responsive sizes // Styling style={{ objectFit: 'cover' }} className="image-class" // Event handlers onLoad={(e) => console.log('Image loaded')} onError={(e) => console.log('Image error')} // Other props unoptimized={false} // boolean - disable optimization />

Advanced Configuration

Configure image optimization in next.config.js:

// next.config.js module.exports = { images: { // Device sizes for responsive images deviceSizes: [640, 750, 828, 1080, 1200, 1920, 2048, 3840], // Image sizes (width) for srcset imageSizes: [16, 32, 48, 64, 96, 128, 256, 384], // Formats to use formats: ['image/avif', 'image/webp'], // Remote patterns (replaces domains) remotePatterns: [ { protocol: 'https', hostname: '**.example.com', port: '', pathname: '/images/**', }, ], // Minimum cache time in seconds minimumCacheTTL: 60, // Disable static imports optimization disableStaticImages: false, // Dangerous allow SVG dangerouslyAllowSVG: true, contentDispositionType: 'attachment', contentSecurityPolicy: "default-src 'self'; script-src 'none'; sandbox;", }, };

Image Gallery Example

Create an optimized image gallery:

// app/gallery/page.tsx import Image from 'next/image'; const images = [ { src: '/gallery/1.jpg', alt: 'Image 1' }, { src: '/gallery/2.jpg', alt: 'Image 2' }, { src: '/gallery/3.jpg', alt: 'Image 3' }, { src: '/gallery/4.jpg', alt: 'Image 4' }, ]; export default function Gallery() { return ( <div className="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-4"> {images.map((image, index) => ( <div key={index} className="relative aspect-square"> <Image src={image.src} alt={image.alt} fill sizes="(max-width: 768px) 50vw, (max-width: 1200px) 33vw, 25vw" style={{ objectFit: 'cover' }} className="rounded-lg" /> </div> ))} </div> ); }

Performance Best Practices

Follow these guidelines for optimal image performance:

1. Use Appropriate Formats

  • JPEG for photographs and complex images
  • PNG for images requiring transparency
  • SVG for icons and logos (inline for small SVGs)
  • Let Next.js automatically convert to WebP/AVIF

2. Optimize Image Dimensions

// Bad: Serving 3000x2000 image for 300x200 display <Image src="/huge-image.jpg" width={300} height={200} /> // Good: Serve appropriately sized image <Image src="/optimized-image.jpg" width={300} height={200} />

3. Use Priority Wisely

// Good: Priority for above-the-fold hero <Image src="/hero.jpg" priority /> // Bad: Priority for many images {products.map(p => ( <Image src={p.image} priority /> // Don't do this! ))}

4. Specify Sizes Accurately

// Matches actual display size at different breakpoints <Image src="/responsive.jpg" sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 800px" />

5. Use Blur Placeholders

import heroImage from './hero.jpg'; <Image src={heroImage} placeholder="blur" // Prevents layout shift />
Exercise: Create a responsive product showcase page with:
  • A hero image with blur placeholder and priority loading
  • A grid of 12 product images with lazy loading
  • Responsive sizing using the sizes prop
  • Proper alt text for accessibility
  • Click to enlarge functionality (bonus)
Ensure proper configuration for remote images if using external CDN sources.

Common Pitfalls and Solutions

Issue: Images not optimizing on production

Solution: Ensure Sharp is installed and you're using a supported hosting platform:

npm install sharp

Issue: Remote images not loading

Solution: Add domain to remotePatterns in next.config.js

Issue: Layout shift when images load

Solution: Always specify width/height or use fill with proper container

Issue: Poor quality images

Solution: Increase quality prop (default is 75, max is 100)

Summary

Next.js Image component provides powerful automatic optimization features. Key takeaways:

  • Use next/image for automatic optimization, lazy loading, and responsive images
  • Specify width/height or use fill for proper layout
  • Configure remotePatterns for external images
  • Use priority prop for above-the-fold images
  • Leverage blur placeholders to prevent layout shift
  • Use sizes prop for responsive images
  • Configure quality based on use case (75-90 for most cases)
  • Modern formats (WebP, AVIF) are automatically served when supported