Next.js Image Optimization Deep Dive
Learn how the next/image component handles responsive sizing, lazy loading, format conversion, and caching to ship faster sites.
What you'll learn
- ✓How next/image works under the hood
- ✓Responsive sizing with sizes and srcset
- ✓When to use priority and placeholder
- ✓Avoiding layout shift with width and height
- ✓Tuning the image cache and remote patterns
Prerequisites
- •Comfortable with HTML and JavaScript
What and Why
Images are usually the heaviest assets on a page. They block the Largest Contentful Paint, eat bandwidth on mobile networks, and cause layout shifts when their dimensions are not declared. Next.js ships a built-in next/image component that handles resizing, modern format conversion, lazy loading, and caching for you. The goal is to deliver the smallest image that still looks sharp for each viewport.
Mental Model
Think of next/image as a small CDN in front of your application. When a request comes in, Next.js looks at the URL, the requested width, and the accepted image formats. If a cached variant exists, it serves it. Otherwise it fetches the source, resizes it, converts it to AVIF or WebP if the browser supports it, stores the result on disk, and returns it with long cache headers.
Hands-on Example
import Image from 'next/image';
import hero from './hero.jpg';
export default function Hero() {
return (
<Image
src={hero}
alt="Mountain at sunrise"
placeholder="blur"
priority
sizes="(max-width: 768px) 100vw, 50vw"
className="rounded-lg"
/>
);
}
When you import a local image, Next.js statically knows its width and height, so it can reserve space and generate a blur placeholder automatically. For remote images, you must pass width and height explicitly and allow the host in next.config.js.
module.exports = {
images: {
remotePatterns: [
{ protocol: 'https', hostname: 'images.unsplash.com' },
],
formats: ['image/avif', 'image/webp'],
},
};
Browser Next.js Image Optimizer Source
GET /img?w=640 -> check disk cache
miss -> fetch original -> 200 OK
resize + encode AVIF
write cache
<- AVIF 640w send with Cache-Control
The sizes prop is the most misunderstood option. It does not change how the image is displayed in CSS. It tells the browser which width to pick from the generated srcset. A good rule is to match the actual CSS width at each breakpoint. If your image takes the full width on phones and half the width on desktop, write (max-width: 768px) 100vw, 50vw.
Common Pitfalls
The biggest mistake is using next/image without sizes for responsive layouts. The browser then downloads the largest variant just in case, which defeats the purpose. Another trap is wrapping the image in a flex container without setting an explicit width or height, which can collapse to zero. If you need a fluid image, set the parent to position: relative and use fill along with a sizes value.
Forgetting priority on the LCP image hurts metrics because the image becomes lazy by default. Conversely, marking every image as priority preloads them all and slows down the initial response. Pick one above-the-fold image per route.
Remote pattern misconfiguration produces confusing 400 errors. The hostname check is strict, including the protocol. If you migrate from an old domains array, make sure each entry has its protocol and optional port set in remotePatterns.
Practical Tips
Run a Lighthouse audit on a slow 4G profile and look at the bytes saved column. If you see large savings, your sizes value is probably wrong. Use the network panel to confirm the served width matches the rendered width.
Set a custom deviceSizes array in the config if your design system has specific breakpoints. The default array covers most cases but generates extra variants you may not need.
For user-uploaded content, consider proxying through a dedicated loader so you can apply watermarks or signed URLs. The loader prop accepts a function that receives src, width, and quality and returns the final URL.
Use quality={75} as a starting point. Lower values produce visible artifacts on photographs but are fine for screenshots. Test on real devices, not just a retina laptop.
Wrap-up
The next/image component is one of the highest-leverage primitives in the framework. With a couple of attributes you can shave hundreds of kilobytes off every page load and lock in stable layouts. Spend a few minutes per template tuning sizes, priority, and your remote patterns, and you will see real improvements in your Core Web Vitals.
Related articles
- Next.js Next.js Caching Strategies Explained
Walk through the four caching layers in Next.js App Router and learn how to choose static, ISR, dynamic, or per-request fetch caching.
- Next.js Next.js Data Fetching Patterns: A Practical Guide
Compare the main Next.js data fetching strategies and learn when to use server components, route handlers, SWR, or static generation in your apps.
- Next.js Next.js Dynamic Routes and Catch-All Segments Explained
Master dynamic routes, catch-all segments, and optional catch-alls in Next.js. Learn how the file-based router maps URLs to components with concrete examples.
- Next.js Next.js Streaming and Suspense Boundaries
Learn how the App Router streams HTML over the wire and how Suspense boundaries control what users see while data loads.