TrueSpec

Image Optimization in Next.js

next/image, blur placeholders, responsive sizes, and lazy loading

What You’ll Build

After following this guide, you will have a working implementation of image optimization in next.js in your project. Next.js Image component automatically optimizes images: serves WebP/AVIF, resizes for the device, lazy loads below the fold, and shows blur placeholders while loading. This can reduce page weight by 50-80% compared to unoptimized images.

Use Cases & Problems Solved

  • Implement interactive UI features that users expect from modern apps
  • Follow established patterns that scale and remain maintainable
  • Reduce boilerplate and avoid common frontend pitfalls

Prerequisites

  • Next.js 13+

Step-by-Step Implementation

Basic optimized image

The following snippet shows how to basic optimized image. Copy this into your project and adjust the values for your environment.

import Image from 'next/image';

// Local image — auto blur placeholder
import heroImage from '../public/hero.jpg';

export function Hero() {
  return (
    <Image
      src={heroImage}
      alt="Hero banner"
      placeholder="blur"
      priority  // Preload for above-the-fold images
      className="w-full h-[400px] object-cover"
    />
  );
}

// Remote image — must configure domain
export function UserAvatar({ src, name }) {
  return (
    <Image
      src={src}
      alt={name}
      width={48}
      height={48}
      className="rounded-full"
    />
  );
}

Responsive image with sizes

The following snippet shows how to responsive image with sizes. Copy this into your project and adjust the values for your environment.

// Serve different sizes based on viewport
<Image
  src="/product.jpg"
  alt="Product"
  fill
  sizes="(max-width: 640px) 100vw, (max-width: 1024px) 50vw, 33vw"
  className="object-cover"
/>

// next.config.js — whitelist remote image domains
module.exports = {
  images: {
    remotePatterns: [
      { protocol: 'https', hostname: '**.amazonaws.com' },
      { protocol: 'https', hostname: 'avatars.githubusercontent.com' },
    ],
  },
};

⚠️ Don’t Do This

❌ Using tags with full-size images

// Serves a 4000x3000 image to a 200px thumbnail!
<img src="/photos/vacation.jpg" width="200" />
// Downloads 5MB when only 20KB is needed

✅ Use next/image — auto-resizes to the displayed size

// Serves a properly sized image (e.g., 400x300 WebP = 20KB)
<Image src="/photos/vacation.jpg" width={200} height={150} alt="Vacation" />
// Automatically serves WebP/AVIF, properly sized

Testing

Verify your implementation with these tests:

// __tests__/image-optimization-in-next-js.test.ts
import { describe, it, expect } from 'vitest';

describe('Image Optimization in Next.js', () => {
  it('should initialize without errors', () => {
    // Test that the setup completes successfully
    expect(() => setup()).not.toThrow();
  });

  it('should handle the primary use case', async () => {
    const result = await execute();
    expect(result).toBeDefined();
    expect(result.success).toBe(true);
  });

  it('should handle edge cases', async () => {
    // Test with empty/null input
    const result = await execute(null);
    expect(result.error).toBeDefined();
  });
});

Verification

# Open DevTools → Network tab → filter by 'Img'
# Check that images are served as WebP/AVIF (not original format)
# Check image dimensions match display size (not oversized)
# Scroll down — images below fold should lazy load
# Run Lighthouse — Image section should be green

Related Specs

Intermediate

Next.js Server Actions Guide

Form mutations, revalidation, optimistic updates, and error handling

Frontend Patterns
Intermediate

Infinite Scroll with Virtualization

TanStack Virtual + Intersection Observer for smooth infinite lists

Frontend Patterns
Beginner

Zustand State Management

Store setup, slices, persist middleware, devtools, and TypeScript

Frontend Patterns