next/image
v4, AVIF by Default, and the Config Changes Teams Miss
This article was written with the assistance of AI, under human supervision and review.
Most Next.js image performance problems in 2026 stem from teams upgrading to v4 without understanding the default format switch to AVIF and the breaking configuration changes that silently degrade production pipelines. The next/image
component shipped automatic WebP conversion in v3, but v4 prioritizes AVIF by default—a format that delivers 20-30% smaller file sizes at equivalent quality but introduces browser compatibility gaps and configuration requirements that break existing deployments.
The failure mode here is subtle but expensive. Applications upgrade to Next.js 15 with next/image
v4, AVIF encoding begins server-side, and teams observe slower image response times on older browsers that fall back to legacy formats. The configuration changes required to maintain v3 behavior—explicit formats
arrays, updated remotePatterns
replacing deprecated domains
, and new cache control settings—are not surfaced during the upgrade process. Production incidents follow when CDN integration breaks, disk cache limits are exceeded, or images fail to load from third-party sources that require the new security model.
Image optimization problem flow showing silent AVIF encoding
This matters because image optimization accounts for 40-60% of total page weight in modern web applications. When the optimization layer silently shifts format priorities without corresponding infrastructure updates, the performance wins teams expect from upgrading evaporate. The solution requires explicit configuration that maintains format flexibility while enabling AVIF where supported, paired with cache strategies that prevent redundant encoding work.
Correct image optimization with explicit format control
next/image
v4, requiring explicit formats
configuration to maintain WebP-first behavior or enable selective AVIF adoption based on browser support.domains
configuration is deprecatedremotePatterns
, which enforces stricter security through protocol, hostname, and pathname matching—breaking existing third-party image integrations.maximumDiskCacheSize
, contentDispositionType
)priority
prop and sizes
attributeNext.js 15 ships next/image
v4 with AVIF as the first format in the default formats
array, replacing the v3 behavior where WebP took priority.
The change reflects browser support evolution—AVIF support crossed 90% global coverage in late 2025, making it a viable default for modern applications. The format delivers superior compression ratios compared to WebP, particularly for photographic content with gradients and high-frequency detail. A typical product image that compresses to 80KB as WebP encodes to 55-60KB as AVIF at perceptually identical quality.
Format priority flow in next/image v4
The breaking change surfaces when teams rely on the v3 implicit format priority. Applications serving images to users on Safari 15 or older Android browsers without explicit fallback configuration will trigger AVIF encoding attempts that fail silently. The image component detects lack of support via the Accept
header and regenerates WebP variants on demand, but this introduces latency spikes on first request and doubles optimization work server-side.
The implication here is that teams must audit their user base browser distribution before enabling AVIF by default. If analytics show 5%+ traffic from pre-AVIF browsers, the cost of redundant encoding outweighs the compression benefit. The correct approach is explicit format configuration in next.config.js
:
import type { NextConfig } from 'next'
const config: NextConfig = {
images: {
formats: ['image/webp', 'image/avif'], // WebP first, AVIF fallback
deviceSizes: [640, 750, 828, 1080, 1200, 1920, 2048, 3840],
imageSizes: [16, 32, 48, 64, 96, 128, 256, 384],
minimumCacheTTL: 60,
},
}
export default config
This configuration prioritizes WebP, serves AVIF only to browsers that explicitly request it via Accept: image/avif
, and maintains backward compatibility with the v3 behavior. Teams can invert the array to ['image/avif', 'image/webp']
once their analytics confirm AVIF support exceeds 95%.
The additional v4 change that breaks production deployments is the removal of the unoptimized
prop default behavior. In v3, setting unoptimized={true}
bypassed the optimization pipeline and served the original image directly. v4 enforces optimization by default and requires explicit ``
configuration to disable processing. Applications that relied on unoptimized
for SVG files or assets served from external CDNs must migrate to custom s or update their remotePatterns
configuration to mark specific domains as unoptimized sources.
The domains
array in next.config.js
is deprecated in Next.js 15, replaced by remotePatterns
which enforces protocol and pathname matching for third-party image sources.
Remote pattern validation flow
The old domains
configuration accepted hostnames only:
// Deprecated v3 configuration
const config: NextConfig = {
images: {
domains: ['cdn.example.com', 'assets.partner.com'],
},
}
This approach allowed any path on the specified domain, creating a security surface where attackers could reference arbitrary URLs under approved domains. The v4 remotePatterns
array requires explicit protocol, hostname, and optional pathname and port matching:
import type { NextConfig } from 'next'
const config: NextConfig = {
images: {
remotePatterns: [
{
protocol: 'https',
hostname: 'cdn.example.com',
pathname: '/images/**',
},
{
protocol: 'https',
hostname: 'assets.partner.com',
port: '',
pathname: '/product-photos/**',
},
],
},
}
export default config
The **
glob pattern matches any nested path structure. Applications serving images from user-generated content platforms or third-party e-commerce APIs must explicitly enumerate each allowed pathname pattern. The failure mode teams encounter is production incidents where images load during local development (because remotePatterns
validation only runs in production builds) but break after deployment when the Next.js optimizer rejects URLs that don't match the configured patterns.
The related configuration change that teams miss is the quality
parameter array. In v3, a single quality
integer applied to all formats. v4 allows per-format quality settings:
const config: NextConfig = {
images: {
formats: ['image/avif', 'image/webp'],
deviceSizes: [640, 750, 828, 1080, 1200, 1920],
// Per-format quality (AVIF can use lower values than WebP)
dangerouslyAllowSVG: false,
contentDispositionType: 'inline',
},
}
AVIF achieves perceptually lossless quality at quality settings 10-15 points lower than WebP. A WebP image at quality 80 is visually equivalent to AVIF at quality 65-70. The cost of not configuring per-format quality is oversized AVIF files that negate the format's compression advantage. Teams should benchmark quality settings with real content using tools like ImageMagick's compare or browser DevTools to establish the lowest acceptable quality per format.
The configuration surface expanded in v4 to include contentSecurityPolicy
for SVG files (when dangerouslyAllowSVG: true
) and contentDispositionType
which controls whether browsers download or display images inline. The default inline
value is correct for most cases, but applications serving user-uploaded PDFs or other document types through the image component must set attachment
to trigger downloads.
AVIF delivers 20-30% smaller file sizes than WebP at equivalent visual quality, but encoding time increases by a factor of 3-5x, creating latency tradeoffs that depend on cache hit rates.
Format comparison showing AVIF benefits and encoding cost
The performance impact manifests in two phases: cold-start encoding latency and ongoing bandwidth savings. When a user requests an image variant that hasn't been cached, the Next.js optimizer encodes it on-demand. WebP encoding for a 1920px product photo completes in 150-250ms on a modern server instance. The same image as AVIF requires 600-1000ms due to the format's computationally intensive encoding algorithm.
This distinction is critical for applications with high image variety and low cache hit rates. E-commerce platforms serving thousands of unique SKU images or content management systems with frequent uploads will observe higher p99 latency on initial image loads when AVIF is enabled. The bandwidth savings compound over time—a site serving 10M image impressions monthly saves 2-3TB of transfer when AVIF replaces WebP—but the encoding cost concentrates at cache misses.
The mitigation strategy is aggressive caching with high TTLs and pre-warming for critical images:
import type { NextConfig } from 'next'
const config: NextConfig = {
images: {
formats: ['image/avif', 'image/webp'],
minimumCacheTTL: 31536000, // 1 year for immutable images
deviceSizes: [640, 750, 828, 1080, 1200, 1920],
},
}
export default config
Applications using CDNs like Cloudflare or Fastly should configure aggressive cache rules that store optimized variants at the edge. The Next.js optimizer sets Cache-Control
headers based on minimumCacheTTL
, but CDN behavior depends on additional configuration. Vercel deployments automatically cache optimized images at the edge, but self-hosted Next.js applications must configure CDN cache keys that include the image URL and requested dimensions.
The browser support gap for AVIF narrows monthly but remains relevant for applications targeting older devices. Safari added AVIF support in version 16 (September 2022), but iOS users on older hardware remain on Safari 15. Teams can implement progressive enhancement by serving WebP to these users via explicit format ordering:
const config: NextConfig = {
images: {
// Serve WebP first, AVIF to browsers that request it
formats: ['image/webp', 'image/avif'],
},
}
The browser sends an Accept
header listing supported formats. When Accept: image/avif,image/webp,*/*
appears, Next.js serves AVIF. Older browsers send Accept: image/webp,*/*
and receive WebP. This approach eliminates encoding waste—AVIF variants are only generated when browsers explicitly request them.
Applications serving 1M+ monthly image impressions or requiring advanced transformations should migrate to custom s that offload optimization to dedicated CDN services.
The built-in Next.js optimizer runs on the application server, consuming CPU and memory during encoding. High-traffic sites experience resource contention when image optimization competes with application request processing. The solution is a custom that delegates to Cloudinary, Imgix, or Cloudflare Images:
// lib/cloudinary-.ts
import type { Image } from 'next/image'
const cloudinary: Image = ({ src, width, quality }) => {
const params = [
'f_auto', // Auto format (AVIF/WebP based on browser)
'c_limit', // Don't upscale
`w_${width}`,
`q_${quality || 'auto'}`,
]
const baseUrl = 'https://res.cloudinary.com/your-cloud/image/upload'
return `${baseUrl}/${params.join(',')}/${src}`
}
export default cloudinary
python
// next.config.ts
import type { NextConfig } from 'next'
const config: NextConfig = {
images: {
: 'custom',
File: './lib/cloudinary-.ts',
},
}
export default config
This configuration bypasses the Next.js optimizer entirely. The Image
component generates Cloudinary URLs with transformation parameters, and Cloudinary handles format negotiation, encoding, and edge caching. The advantage is zero server-side optimization cost—application servers return faster, and image processing scales independently.
The tradeoff is vendor lock-in and cost structure. Cloudinary charges based on transformation volume, with pricing that exceeds self-hosted optimization at high scale. Teams must calculate the crossover point where CDN costs exceed the infrastructure savings from off optimization work. For most applications, this threshold sits around 5-10M monthly transformations.
The alternative approach for self-hosted deployments is a custom that points to a dedicated image optimization service running in the same infrastructure:
const custom: Image = ({ src, width, quality }) => {
const params = new URLSearchParams({
url: src,
w: width.toString(),
q: (quality || 75).toString(),
})
return `https://images.yourdomain.com/optimize?${params}`
}
This service runs sharp or libvips directly, providing the same optimization capabilities as the built-in Next.js optimizer but on dedicated infrastructure that can scale horizontally without affecting application servers. The implementation complexity is higher—teams must build the optimization API, configure caching layers, and handle security—but it preserves format flexibility and avoids vendor dependencies.
The decision point is simple: if image optimization consumes more than 15% of application server CPU during peak traffic, migrate to a dedicated solution. Below that threshold, the built-in optimizer's simplicity outweighs the operational overhead of custom infrastructure.
Next.js caches optimized images in .next/cache/images
on disk, with no default size limit, leading to disk exhaustion on long-running production instances.
Cache lifecycle showing disk limit enforcement
The maximumDiskCacheSize
configuration prevents this failure mode:
import type { NextConfig } from 'next'
const config: NextConfig = {
images: {
minimumCacheTTL: 60,
// Limit disk cache to 500MB (default is no limit)
// @ts-expect-error - New in Next.js 15
maximumDiskCacheSize: 500 * 1024 * 1024,
formats: ['image/avif', 'image/webp'],
},
}
export default config
When the cache directory exceeds 500MB, Next.js evicts the least-recently-used entries until size drops below the limit. This prevents disk exhaustion but introduces a subtle failure mode: high-traffic applications serving diverse image content may thrash the cache, evicting entries that will be requested again soon. The symptom is elevated encoding latency as popular images are re-optimized repeatedly.
The solution is right-sizing the cache limit based on actual image diversity. Applications serving a fixed set of product images (e-commerce) can use smaller limits because the working set stabilizes. Content platforms with user-generated uploads require larger limits or must offload optimization to a CDN that provides effectively unlimited cache capacity.
The related configuration that teams overlook is contentDispositionType
, which controls the Content-Disposition
header on optimized images:
const config: NextConfig = {
images: {
contentDispositionType: 'inline', // Default, display in browser
// Set to 'attachment' to force download
},
}
The default inline
value is correct for images displayed in pages. Applications serving downloadable assets (user-uploaded documents converted to images, PDF previews) must set attachment
to trigger browser download prompts. The failure mode is users attempting to download files that instead display inline, requiring right-click "Save As" workarounds that degrade UX.
The cache behavior interacts with the minimumCacheTTL
setting, which controls how long Next.js caches optimized images before revalidating. The default 60 seconds is conservative—most applications should increase this to match their content update frequency:
const config: NextConfig = {
images: {
minimumCacheTTL: 31536000, // 1 year for immutable images
deviceSizes: [640, 750, 828, 1080, 1200, 1920],
},
}
Images with cache-busting parameters (query strings or hashed filenames) can use year-long TTLs safely. This eliminates redundant revalidation and maximizes cache hit rates. Applications serving dynamic images that update frequently (user avatars, real-time chart snapshots) must balance cache TTL against content freshness requirements.
The priority
prop on Image
components marks images for eager but does not automatically inject preload links in the document head, requiring manual configuration in layouts.
Priority image flow showing manual preload requirement
Teams mark hero images with priority={true}
expecting immediate load initiation, but the browser doesn't discover the image until React hydration completes. The correct approach adds explicit preload links in the root layout:
// app/layout.tsx
import type { Metadata } from 'next'
export const metadata: Metadata = {
title: 'Your App',
}
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<html lang="en">
<head>
<link
rel="preload"
as="image"
href="/_next/image?url=/hero.jpg&w=1920&q=75"
imageSrcSet="/_next/image?url=/hero.jpg&w=640&q=75 640w, /_next/image?url=/hero.jpg&w=1920&q=75 1920w"
imageSizes="100vw"
/>
</head>
<body>{children}</body>
</html>
)
}
This initiates the hero image load in parallel with HTML parsing, eliminating the discovery delay. The priority
prop still matters—it prevents lazy and ensures the image isn't deferred—but the preload link provides the actual performance benefit for above-fold content.
The second common pitfall is sizes
attribute misconfiguration. The sizes
prop tells the browser which image variant to select based on viewport width:
<Image
src="/product.jpg"
alt="Product"
width={1200}
height={800}
sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw"
/>
When sizes
is omitted or incorrect, the browser selects the largest available variant regardless of actual display size. A product thumbnail displayed at 300px width will load the 1920px variant, wasting bandwidth. The failure mode is subtle—images render correctly, but transfer sizes are 3-5x larger than necessary.
The correct approach is auditing actual image display sizes in production using browser DevTools and configuring sizes
to match. The syntax accepts CSS media queries and viewport-relative units:
100vw
— full viewport width (mobile hero images)50vw
— half viewport width (two-column layouts)(max-width: 768px) 100vw, 33vw
— full width on mobile, one-third on desktopThe third pitfall is omitting width
and height
props, which causes layout shift as images load. Next.js requires dimensions for proper aspect ratio calculation:
// Incorrect - causes layout shift
<Image src="/product.jpg" alt="Product" />
// Correct - reserves space, prevents shift
<Image
src="/product.jpg"
alt="Product"
width={1200}
height={800}
/>
For images with unknown dimensions, use fill
mode with a positioned container:
<div style={{ position: 'relative', width: '100%', height: '400px' }}>
<Image
src="/dynamic.jpg"
alt="Dynamic"
fill
style={{ objectFit: 'cover' }}
/>
</div>
This approach works for user-generated content where dimensions aren't known at build time. The container reserves space, preventing layout shift, and objectFit
controls how the image fills the container.
Enable AVIF when analytics show 95%+ browser support and cache hit rates exceed 80%. Below these thresholds, the encoding cost outweighs bandwidth savings.
remotePatterns
enforces protocol, hostname, and pathname matching for security, while domains
accepted any path on approved hostnames. Migrate existing domains
entries to explicit remotePatterns
with pathname wildcards.
The default configuration has no maximumDiskCacheSize
limit. Set an explicit limit based on available disk space and image diversity to prevent production failures.
The priority
prop prevents lazy but doesn't inject preload links. Add explicit <link rel="preload">
tags in layouts for above-fold images to trigger immediate load initiation.
Audit actual display widths in DevTools and configure sizes
with media queries matching your breakpoints. Use viewport-relative units (vw
) for fluid layouts and fixed pixel values for constrained containers.
That covers the essential patterns for Next.js image optimization in 2026. Apply these in production and the difference will be immediate:
formats
arraysnext.config.ts
to control AVIF adoption based on browser analytics.domains
to remotePatterns
maximumDiskCacheSize
sizes
The Next.js image component remains the most accessible optimization solution for modern web applications, but the v4 defaults and configuration surface require deliberate choices that match infrastructure reality. Teams that treat image optimization as a configuration exercise rather than an automatic feature gain measurable performance improvements without the operational complexity of dedicated CDN services.