Next.js

Next.js Image Optimization👨‍💻

Images account for the largest portion of page weight on most websites. A single unoptimized hero image can tank your Largest Contentful Paint (LCP) score and push your Core Web Vitals into the red. The next/image component exists to solve this problem at the framework level — automatic format negotiation (WebP, AVIF), responsive srcset generation, lazy loading by default, and built-in layout shift prevention.

The component is not just a convenience wrapper around <img>. It is an image optimization pipeline: your original source image goes in, and the browser receives the smallest possible file in the best supported format, sized exactly for the device viewport. You get this with zero manual work beyond choosing the right props.

Key Takeaways

  • 1The `next/image` component generates responsive `srcset` attributes and negotiates modern formats (WebP, AVIF) automatically based on the browser's `Accept` header
  • 2Every image requires explicit `width` and `height` (or `fill`) to reserve layout space and prevent Cumulative Layout Shift — the browser needs dimensions before the image downloads
  • 3The `sizes` prop controls which image variant the browser picks from the `srcset`. Without it, the browser assumes 100vw and downloads an oversized image
  • 4Use `loading="eager"` or the `preload` prop for your LCP image (typically the hero) — everything else should stay lazy-loaded by default
  • 5Static imports (`import hero from './hero.jpg'`) give you automatic blur placeholders and content-hashed URLs for immutable caching
  • 6Remote images require `remotePatterns` in `next.config.js` — this is a security boundary, not just configuration

Master next.js image optimization

Take the Next.js Full-Stack course with hands-on lessons and challenges.

Examples

Responsive hero image with preload for LCP

tsx

The hero image uses fill to span its parent container, sizes="100vw" because it is full-width, and preload so the browser starts fetching it immediately (this is your LCP element). The static import gives you a free blur placeholder while the full image loads. No width/height needed with fill — the parent's dimensions control the rendered size.

Product image grid with responsive sizes

tsx

The sizes prop matches the grid layout: 50vw on mobile (2 columns), 33vw on tablet (3 columns), 25vw on desktop (4 columns). This tells the browser exactly how wide the image renders at each breakpoint, so it picks the smallest sufficient variant from the srcset. Without sizes, every product image would download at 100vw — 2x to 4x larger than necessary.

User avatar with fill mode and fallback

tsx

Avatars are a textbook case for fill mode — the container is a fixed circle, and you want the image to cover it regardless of source dimensions. The sizes prop is set to the pixel size of the avatar, so the browser downloads the smallest variant that covers it (no point loading a 1920px image for a 48px circle). The onError fallback shows initials when the remote URL fails.

Remote image configuration with blur placeholder

typescript

remotePatterns is your allowlist — only these origins get optimized. Be specific with pathnames (don't leave it open to the entire hostname). The formats array enables AVIF with WebP fallback: AVIF compresses ~20% smaller but encodes slower on first request. Setting qualities to [50, 75] limits the optimization API to only those two levels, preventing abuse on self-hosted deployments.

Art direction with getImageProps for mobile/desktop

tsx

Art direction means serving a different crop, not just a different size. A wide panoramic shot works on desktop but loses detail on mobile — you want a square crop instead. getImageProps generates the optimized srcset without rendering an Image component, so you can use a standard <picture> element with media queries. Both sources get Next.js optimization (format negotiation, responsive widths).

Common Mistakes

Mistake:

Omitting the `sizes` prop on responsive images — the browser defaults to assuming the image is 100vw wide and downloads a massive variant

Fix:

Always set `sizes` to match your CSS layout. If an image sits in a 3-column grid, use `sizes="(max-width: 768px) 100vw, 33vw"`. This is the single highest-impact optimization you can make after using the component at all.

Mistake:

Setting `preload` (or the deprecated `priority`) on every image above the fold — this blocks rendering while all of them race to download

Fix:

Only one image per page should get `preload`: the one that drives your LCP score. For most pages that is the hero image or the main content image. Everything else should lazy-load or use `loading="eager"` if it is critical but not LCP.

Mistake:

Using `fill` without setting `position: relative` on the parent — the image either overflows or collapses to zero height

Fix:

The parent must have `position: relative` (or `absolute`/`fixed`) and explicit dimensions. A common pattern is `<div className="relative aspect-video">` to let Tailwind handle both the positioning and the aspect ratio.

Mistake:

Adding remote image hostnames to the old `domains` array instead of `remotePatterns` — this provides no path restriction and is deprecated

Fix:

Use `remotePatterns` with specific `protocol`, `hostname`, `pathname`, and `search` fields. This limits optimization to exactly the URLs you expect, preventing your image API from being used as a proxy for arbitrary external content.

Best Practices

  • Use static imports for local images whenever possible — you get automatic dimensions, content-hashed URLs for immutable caching, and free blur placeholders with zero configuration
  • Always set `sizes` to reflect the image's rendered width at each breakpoint. A 4-column grid image should never claim `100vw` — that wastes bandwidth on every single page load
  • Mark your LCP image with `preload` and give it an explicit `fetchPriority="high"`. Run Lighthouse to confirm which element is actually LCP before guessing
  • Enable AVIF alongside WebP in your `formats` config for maximum compression. The ~20% size reduction over WebP is meaningful at scale, and the encoding cost only hits the first request
  • Set `placeholder="blur"` on large above-the-fold images to improve perceived performance. For remote images, generate a tiny blurDataURL (10px wide) server-side with a library like plaiceholder

Summary

The `next/image` component handles format negotiation, responsive srcset generation, lazy loading, and layout shift prevention — but only if you use it correctly. The sizes prop is non-negotiable for responsive layouts (without it, every image downloads at viewport width). Use fill mode when the container controls the dimensions, static imports for local assets with free blur placeholders, and preload only for the single LCP image. Configure remotePatterns tightly, enable AVIF for maximum compression, and let the framework do what it was built for.

Practice Next.js with hands-on challenges

Learn next.js image optimization hands-on in your IDE

Interactive lessons and challenges on Stanza, practice in VS Code, Cursor, or the web.

Related Concepts

Related Cheatsheets

Master Next.js with Stanza

Interactive lessons and challenges, right in your code editor.

Check the free courses. No credit card.