Suggest an editImprove this articleRefine the answer for “Image optimization (next/image) in Next.js”. Your changes go to moderation before they’re published.Approval requiredContentWhat you’re changing🇺🇸EN🇺🇦UAPreviewTitle (EN)Short answer (EN)**`next/image`** is the Next.js Image component that replaces `<img>` with automatic format conversion to WebP/AVIF, device-specific size variants via srcset, and lazy loading by default. ```tsx <Image src="/hero.jpg" alt="Hero" width={1200} height={600} priority /> ``` **Key point:** without `sizes` on `fill` images, the browser downloads desktop-size variants on every device.Shown above the full answer for quick recall.Answer (EN)Image**`next/image`** is the Next.js Image component that automatically converts images to WebP/AVIF, generates device-specific size variants via srcset, and lazy-loads them by default. ## Theory ### TL;DR - Regular `<img>` ships the full original file to every device; `next/image` generates exact-fit variants and delivers only what fits the screen - Main gain: 35-50% smaller files on mobile, because the browser downloads a 400px WebP instead of a 2000px JPEG - Default behavior: lazy loading, format conversion, CLS prevention via reserved dimensions - `priority={true}` only for LCP images (hero banners, above-the-fold content); use it on 1-2 images per page at most - External images require `remotePatterns` in `next.config.js` or you get a runtime error ### Quick example ```tsx import Image from 'next/image' // Regular img: downloads the full 2000x1200px original every time (~2MB) <img src="/hero.jpg" alt="Hero" width={2000} height={1200} /> // next/image: serves ~300KB WebP at actual device size, lazy by default export default function Hero() { return ( <Image src="/hero.jpg" alt="Hero" width={2000} height={1200} priority // above the fold, so preload it /> ) } // Mobile gets a 400w WebP; desktop gets 1200w; browser picks via srcset ``` One thing worth noting from production: skipping the `sizes` prop on a `fill` image means the browser defaults to 100vw at every breakpoint, so a sidebar thumbnail ends up downloading a full-width variant. Small oversight, real bandwidth cost. ### Key difference The browser has no idea how big your `<img>` will actually render until it downloads the full CSS and calculates layout. So it plays safe and fetches the original. `next/image` generates 8+ sized variants at build time and outputs a `<picture>` element with `srcset` descriptors. The browser picks the right variant before the download starts, using `w` descriptors and your `sizes` hint. No guessing, no waste. ### When to use - Hero and above-the-fold images: add `priority` to skip lazy loading and inject a `<link rel="preload">` in the `<head>` - Gallery and card images: default lazy loading with `sizes` set to the actual rendered width (e.g. `"(max-width: 768px) 100vw, 33vw"`) - Images with dimensions unknown at build time: `fill` prop inside a positioned parent container - External URLs from Cloudinary, Unsplash, GitHub avatars: configure `remotePatterns` in `next.config.js` - Dynamic images with a loading state: `placeholder="blur"` with `blurDataURL` for a smooth transition ### How it works internally At build time Next.js uses Sharp to generate WebP, AVIF, and JPEG variants at multiple sizes (roughly 25%, 50%, 75%, and 100% of the original, plus device-specific breakpoints). These get cached in `.next/cache/images`. For `placeholder="blur"`, it encodes a tiny base64 blurhash. At runtime the component outputs a `<picture>` element with MIME-typed `srcset` entries: `image/avif` first, then `image/webp`, then the original format. The browser picks via the `Accept` header. AVIF gives about 75% smaller files than JPEG. WebP gives around 30-50%. Next.js tries AVIF first through content negotiation, falls back to WebP, then JPEG. ISR and SSG both use the same cache, so repeat requests skip Sharp entirely. ### Common mistakes **Missing `width` and `height`** ```tsx // Wrong: no optimization, layout shift, console error <Image src="/me.jpg" alt="Me" /> // Fix: always provide dimensions <Image src="/me.jpg" alt="Me" width={800} height={600} /> ``` **`fill` without a positioned parent** ```tsx // Wrong: image disappears or overflows <div> <Image src="/photo.jpg" fill alt="Photo" /> </div> // Fix: parent needs position: relative and explicit dimensions <div className="relative w-full h-64"> <Image src="/photo.jpg" fill alt="Photo" className="object-cover" /> </div> ``` **External URL without `remotePatterns`** ```js // Throws at runtime: "hostname is not configured under images" // Fix in next.config.js: module.exports = { images: { remotePatterns: [ { protocol: 'https', hostname: 'images.unsplash.com' } ] } } ``` **`priority` on every image.** That loads everything upfront, defeats lazy loading, and slows initial page load. `priority` belongs only on the LCP element. On most pages that is one image. **`sizes="100vw"` on grid items.** A 3-column card grid renders each card at roughly 33vw on desktop. `sizes="100vw"` makes the browser download the 1200px variant when it only needs 400px. Set `sizes` to match the actual rendered width. ### Real-world usage - Vercel homepage: hero banners with `priority` and breakpoint-specific `sizes` - Stripe docs: code screenshot galleries using `fill` and blur placeholders - TailwindUI: product cards pulling images from Cloudinary via `remotePatterns` - GitHub: user avatars served from `avatars.githubusercontent.com` configured in `remotePatterns` - Regular `<img>` still makes sense for SVGs and base64 icons under 10KB, or when passing `unoptimized={true}` to skip the pipeline entirely ### Follow-up questions **Q:** How does `next/image` choose between AVIF and WebP? **A:** It checks the browser's `Accept` header. If `image/avif` is listed, it serves AVIF (about 75% smaller than JPEG). If not, it tries WebP. Falls back to the original format last. **Q:** What does the `sizes` prop actually do? **A:** It tells the browser how wide the image will render at each breakpoint, before the CSS loads. Without it, the browser assumes 100vw and downloads a larger variant than needed. For a 300px sidebar image, `sizes="300px"` cuts the download noticeably. **Q:** Difference between `placeholder="blur"` and `placeholder="empty"`? **A:** `blur` shows a low-res base64 preview while the full image loads. `empty` shows nothing. Use `blur` for large hero images where a visible loading state improves perceived speed. Use `empty` for small thumbnails where the blurred preview would look worse than just waiting. **Q:** Why does `next/image` sometimes increase build time significantly? **A:** Sharp processes every unique image at every size variant. A gallery with 200 images and 8 size breakpoints means 1600 Sharp operations. Setting `images.minimumCacheTTL` keeps cached variants alive across deploys, and using `priority` selectively limits what gets pre-processed. **Q:** (Senior) You have ISR pages with dynamic product images in S3. Optimization runs at request time but you need responses under 100ms. How do you handle this? **A:** Two options. First, configure a custom `loader` pointing to CloudFront with Lambda@Edge for on-the-fly resizing; Next.js skips its own Sharp pipeline and lets the CDN handle it. Second, limit generated variants with `deviceSizes` and `imageSizes` in `next.config.js`, then warm the cache with a post-deploy script hitting each ISR path. The `.next/cache/images` directory persists across deploys on Vercel by default. ## Examples ### Basic: Profile card with a local image ```tsx import Image from 'next/image' export default function ProfileCard({ user }) { return ( <div className="flex items-center gap-4"> <Image src="/avatars/default.png" alt={user.name} width={64} height={64} className="rounded-full" /> <p>{user.name}</p> </div> ) } // Next.js serves a 64x64 WebP variant; the original stays on disk untouched ``` `width` and `height` match the rendered size, so no oversized download happens. No `priority` because this card is never above the fold. ### Intermediate: Blog post card with responsive fill ```tsx import Image from 'next/image' export default function BlogCard({ post }) { return ( <div className="relative w-full h-64"> <Image src={post.coverImage} alt={post.title} fill sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw" className="object-cover rounded-lg" placeholder="blur" blurDataURL={post.blurDataURL} /> <div className="absolute inset-0 bg-gradient-to-t from-black/50" /> </div> ) } // Mobile: downloads ~390w WebP // Tablet: ~640w WebP // Desktop 3-column grid: ~400w WebP ``` The `sizes` prop is doing the real work here. Without it, every device downloads the widest variant. With it, mobile gets a file sized for mobile. ### Advanced: External image with a custom loader for Unsplash ```js // next.config.js module.exports = { images: { remotePatterns: [ { protocol: 'https', hostname: 'images.unsplash.com' } ] } } ``` ```tsx import Image from 'next/image' // Custom loader passes Unsplash's own resize API params function unsplashLoader({ src, width, quality }) { return `${src}?w=${width}&q=${quality || 75}&auto=format` } export default function UnsplashPhoto({ id, alt }) { return ( <Image loader={unsplashLoader} src={`https://images.unsplash.com/photo-${id}`} alt={alt} width={1200} height={800} quality={75} /> ) } // Output URL: https://images.unsplash.com/photo-123?w=1200&q=75&auto=format // Note: custom loader bypasses Next.js Sharp; Unsplash CDN handles resizing ``` The `loader` prop replaces Next.js's default URL builder. Useful when the image source has its own resize API (Cloudinary, Imgix, Unsplash). The `remotePatterns` config is still required even with a custom loader.For the reviewerNote to the moderator (optional)Visible only to the moderator. Helps review go faster.