Next.js Image Optimization with next/image: AVIF, Remote Patterns, and Core Web Vitals (2026)
How to use next/image in Next.js 16 to ship AVIF/WebP, configure remotePatterns safely, add blur placeholders for remote images, and lock down LCP for Core Web Vitals.
Next.js image optimization is the built-in process where the next/image component automatically converts, resizes, and serves images in modern formats like AVIF and WebP, with lazy loading and a CDN cache. In 2026 with Next.js 16, the component generates responsive srcset attributes, prevents layout shift via required width/height props, and serves images through the Image Optimization API at /_next/image. Used correctly, it can cut your Largest Contentful Paint (LCP) by 40–60% and basically eliminate cumulative layout shift (CLS).
The next/image component serves AVIF first, falling back to WebP and the original format, reducing image bytes by 30–80% versus raw JPEG/PNG.
Remote images require an explicit remotePatterns entry in next.config.ts (wildcard hostnames are blocked for security reasons).
Use priority for above-the-fold images (the LCP target) and a blurDataURL placeholder for everything else; this combo wins Core Web Vitals.
The sizes prop is mandatory whenever you use fill or relative widths. Skip it and the browser downloads the largest variant.
Self-hosted Next.js needs sharp installed in production. Vercel includes it automatically through the Image Optimization service.
Static imports give you an automatic blurDataURL and dimensions; dynamic remote URLs do not, so you have to compute them yourself.
How does next/image work under the hood?
When you import Image from next/image and render it, Next.js intercepts the request and routes it through the Image Optimization API mounted at /_next/image. That endpoint reads the source URL, downloads or reads the file, decodes it with the sharp library, resizes it to the closest width in your deviceSizes array, and re-encodes it as AVIF or WebP based on the browser's Accept header. The result is cached on disk (or on Vercel's edge CDN) keyed by URL, width, and quality. Subsequent requests are served straight from cache with a long Cache-Control header.
The component emits a <picture> element wrapping an <img> with a complete srcset covering your configured device sizes (640, 750, 828, 1080, 1200, 1920, 2048, 3840 by default). The browser then picks the best variant based on the viewport and DPR. The catch: the component requires you to provide intrinsic dimensions, either through static import inference or explicit width/height props, so the browser reserves space and avoids cumulative layout shift.
The simplest case is a static import:
import Image from 'next/image';
import hero from './hero.jpg';
export default function Page() {
return (
<Image
src={hero}
alt="Mountain landscape at sunrise"
placeholder="blur"
priority
/>
);
}
Because hero is a static import, the Next.js build pipeline knows the exact width, height, and base64 blur placeholder at compile time. No runtime cost, perfect layout stability. Static imports are also the only mode where the bundler can fingerprint the file and emit an immutable cache header, which means CDN purges on redeploy happen automatically when the file content changes. Honestly, if you can use static imports, you should.
How do I configure remote images in Next.js?
For any image hosted on a domain other than your own (a CMS, S3 bucket, user uploads, Cloudinary, you name it) you must register the hostname in next.config.ts via the images.remotePatterns array. As of Next.js 15.3, the older images.domains option is gone. remotePatterns is the only supported mechanism now, because it forces you to be explicit about the protocol and path prefix.
The hostname field accepts a wildcard subdomain like **.amazonaws.com, but never a wildcard for the entire pattern. That would let an attacker proxy arbitrary remote content through your origin. The pathname field is optional but I'd recommend it; restricting to /uploads/** prevents the optimizer from being used as a generic image proxy that could be abused to cache illegal or unrelated content under your domain's reputation.
Once configured, render the remote image with explicit dimensions. Unlike static imports, Next.js cannot infer them at build time, and rendering an Image without dimensions throws a build error:
If your dimensions are unknown (think user-uploaded photos with variable aspect ratios) use the fill prop with a positioned parent container. I cover that in the responsive layout section below.
AVIF, WebP, and modern image formats
By default, Next.js 16 serves WebP, with AVIF available opt-in. AVIF compresses 20–40% smaller than WebP at the same visual quality, but it costs roughly 10× more CPU time to encode, which matters on cold cache requests. Enable both formats in priority order:
With AVIF first, the optimizer checks the Accept header and serves AVIF to browsers that announce support (Chrome, Firefox, Safari 16+, Edge). Older clients receive WebP, and the long-deprecated tail receives the original JPEG or PNG. The minimumCacheTTL setting controls how long the optimized output lives in the CDN edge cache. One year is safe because the cache key includes the source URL, so any change to the source effectively generates a fresh cache entry.
You can also tune compression quality globally or per-image. The default is 75, which is visually indistinguishable from the source for most photographs:
<Image src={photo} alt="..." quality={60} />
Quality below 50 produces visible banding in flat-color regions (skies, gradients are the usual offenders). Above 85 the byte savings flatten out. For hero images on a marketing page, stick with 75 and rely on the format upgrade rather than aggressive compression. For thumbnails in a high-density grid (product listings, photo galleries), quality 60 is a sensible compromise. I hit this exact tradeoff shipping a photo gallery last year, and dropping the gallery thumbs to q=60 saved about 35% on bandwidth with zero user complaints.
Sizes, fill, and responsive layouts
The sizes prop tells the browser which slot the image will occupy at each viewport. Without it, the browser has to assume the image takes the full viewport width and downloads the largest srcset entry, wasting bandwidth on mobile and inflating LCP. A correct sizes value is a comma-separated list of media queries paired with widths:
The fill prop tells the component to stretch absolutely to its parent's bounding box. The parent must have position: relative (or absolute/fixed) and a defined height, otherwise the image collapses to zero and silently disappears. Use objectFit: 'cover' to crop, or 'contain' to letterbox.
For images with a known aspect ratio, prefer fixed width and height with sizes. The rendered <img> automatically gets width: 100%; height: auto styling, scaling fluidly without absolute positioning. The aspect ratio is preserved by the intrinsic dimensions, eliminating CLS. This is the right default for blog post images, product cards, and avatars: simpler markup, no positioned parent, fewer ways for the layout to go sideways at narrow viewports.
Blur placeholders and loading states
A blurred low-resolution placeholder smooths the perceived load time and signals to users that content is coming. For static imports, Next.js generates the placeholder automatically. Just add placeholder="blur":
import hero from './hero.jpg';
<Image src={hero} alt="..." placeholder="blur" />
For remote images you have to compute and pass a blurDataURL manually. The common pattern is to generate a 10×10 base64 JPEG at build or upload time, store it next to the image record in your CMS, and pass it through. The plaiceholder library generates these on the fly during data fetching:
Computing placeholders on every render is expensive. Cache the result per image URL in your database, in Redis, or use the new "use cache" directive in Next.js 16 Cache Components so the blur hash is computed once and reused. For a CMS-driven blog, the cleanest pattern is to compute the blur at upload time inside an asset pipeline and persist the data URL alongside the image record.
Priority loading and LCP optimization
The Largest Contentful Paint (LCP) metric measures when the largest above-the-fold element finishes rendering. On most pages, that element is an image: a hero photo, a product shot, or an article cover. By default, next/image lazy-loads everything, which means the LCP image waits behind JavaScript parsing and other resources. Mark the LCP candidate with priority:
Setting priority does three things. It omits loading="lazy", it injects a <link rel="preload"> in the document head so the browser starts fetching during HTML parse, and it sets fetchpriority="high" on the img element. Use priority on exactly one image per route (the hero, not every above-the-fold thumbnail) or you dilute the preload signal and the browser ends up splitting bandwidth across competing requests.
Pair priority with a tight sizes value matching the actual rendered width. A frequent mistake (I made this one myself on a client site) is leaving sizes="100vw" on a 600px-wide hero card, which preloads a 3840px AVIF and tanks LCP on mobile. Measure your final LCP in Chrome DevTools' Performance panel under "Largest Contentful Paint" before declaring victory, and cross-check with field data via the web.dev LCP guide.
Custom loaders: Cloudinary, Imgix, and self-hosted
If you already pay for an image CDN like Cloudinary, Imgix, or imgproxy, bypass the built-in optimizer and let the CDN handle resizing. Configure a loader in your config or per-image:
The loader function receives src, width, and quality and returns a fully qualified URL. The component still emits a complete srcset by calling the loader once per device size, so you get responsive variants without running the Next.js optimizer at all. That's essential for serverless deployments where transformation time counts against your function budget.
For self-hosted Next.js without an external CDN, install sharp as a production dependency so the built-in optimizer can run: npm install sharp. There's more in our self-hosting Next.js with Docker guide, which covers the multi-stage build pattern that bundles sharp in the runtime image and persists the on-disk image cache across container restarts.
Troubleshooting common errors
Three errors dominate StackOverflow searches for Next.js images. First, "hostname is not configured under images in your next.config" means the remote URL doesn't match any remotePatterns entry. Double-check the protocol (http vs https), the hostname (subdomain vs apex), and any path restrictions. Second, "Image with src has either width or height modified" appears when one dimension is set via CSS without the other auto-scaling; add style={{ height: 'auto' }} when only constraining width. Third, "missing required width/height" on remote images is fixed by either supplying explicit dimensions or switching to fill with a sized parent.
A subtler bug: images blur permanently on production. This happens when blurDataURL is malformed (a non-base64 string, or a data URL without the correct MIME type). Validate the format with the data:image/jpeg;base64,... prefix and decode a sample to confirm it's a valid image. For images flickering during navigation, ensure your placeholder is served alongside the main HTML (not fetched separately), which is automatic with static imports but requires SSR-resolved data for remote sources.
When debugging Core Web Vitals regressions related to images, pair this guide with our Next.js SEO and metadata guide, which covers dynamic OG image generation. Those images use the same optimization pipeline and influence social-share LCP indirectly through previewing on platforms like Slack and LinkedIn.
Frequently Asked Questions
What is the difference between next/image and the standard img tag?
The next/image component automatically generates responsive srcset entries, converts to AVIF/WebP, lazy-loads by default, and prevents layout shift by requiring intrinsic dimensions. A raw <img> serves the original file with no optimization, no format negotiation, and no built-in LCP signals.
Does next/image support AVIF?
Yes. Set images.formats: ['image/avif', 'image/webp'] in next.config.ts. AVIF is served when the browser's Accept header includes image/avif; otherwise the optimizer falls back to WebP and then the original format.
Why is my Next.js image not loading on production?
The two most common causes are a missing remotePatterns entry for the source hostname and a missing sharp dependency on a self-hosted deployment. Run npm ls sharp in your production image and confirm the optimizer's hostname allowlist matches the URL exactly, including subdomain and protocol.
Should I always use priority on hero images?
Use priority on the single LCP-candidate image per route, usually the hero or first above-the-fold visual. Setting it on multiple images dilutes the preload hint and can actually slow LCP because the browser splits bandwidth across competing high-priority requests.
How do I add a blur placeholder for remote images?
Generate a base64-encoded 10×10 JPEG at build or upload time (libraries like plaiceholder do this with sharp), store it alongside the image record, and pass it as blurDataURL with placeholder="blur". Static imports get this automatically without any extra work.
Build instant-feeling forms and lists in Next.js with useOptimistic, useActionState, and useFormStatus. Production patterns, type-safe rollback, and real Server Action examples.
Build type-safe forms in Next.js with React Hook Form, Zod, and Server Actions. Includes useActionState, file uploads, error handling, and progressive enhancement patterns for Next.js 15 and 16.