Images are the heaviest asset on most content pages and the most common Largest Contentful Paint (LCP) offender. next/image automates responsive sizing, modern formats, lazy loading, and CDN delivery for free. But Next.js 16 (default bundler is now Turbopack, as of June 2026 the current line is 16.2) changed three defaults that silently break sites upgrading from 14/15: priority is deprecated, qualities is now required in config, and the cache TTL dropped from a long default to 4 hours. This guide covers the working setup plus the exact breakages to fix.
TL;DR
- Use
preloadinstead ofpriorityon your LCP image.priorityis deprecated in Next.js 16. - Add
qualitiestonext.config.js— it’s required as of Next.js 16, or the optimizer returns400 Bad Request. - Set
formats: ['image/avif', 'image/webp']— AVIF is opt-in (default is WebP only) and compresses about 20% smaller. - Always set
sizesso the browser fetches a width that matches your layout, not the largest indeviceSizes. - Bump
minimumCacheTTLfrom the new 4-hour default (14400) to something longer if your images are stable.
What changed in Next.js 16
If your config and <Image> calls were written for Next.js 14 or 15, three things now behave differently (per the official version history, v16.0.0):
| Item | Next.js 15 | Next.js 16 (as of June 2026) |
|---|---|---|
| LCP opt-in prop | priority | preload (priority deprecated) |
qualities config | optional | required; default [75] |
minimumCacheTTL default | long-lived | 14400 (4 hours) |
formats default | ['image/webp'] | ['image/webp'] (AVIF still opt-in) |
| Disk cache cap | none | maximumDiskCacheSize, defaults to 50% of free disk |
The practical upshot: a config that worked last year may now throw on quality values or quietly re-fetch optimized images every four hours.
How to tell you have a problem
- PageSpeed Insights flags “Properly size images” or “Serve images in next-gen formats.”
- Your LCP element is a hero image and mobile LCP is over 2.5s (the “good” Core Web Vitals threshold).
- The optimizer returns
400 Bad Requestafter upgrading — usually an unallowedqualityvalue now thatqualitiesis enforced. next/imagethrowsInvalid src prop, hostname is not configuredon deploy.- You see Cumulative Layout Shift because images load after text.
Step by step
- Replace
<img>withnext/image. A static import gives you freewidth/heightinference and an automatic blur placeholder:
import Image from 'next/image';
import heroImg from '@/public/hero.png';
export default function Page() {
return (
<Image
src={heroImg}
alt="Dashboard showing weekly metrics"
placeholder="blur"
sizes="(max-width: 768px) 100vw, 800px"
preload // LCP image only — see step 2
/>
);
}
-
LCP image gets
preload, notpriority. In Next.js 16 thepriorityprop is deprecated in favor ofpreload, which inserts a<link rel="preload">in the<head>. Mark only one image per page — typically the hero, never a footer logo. The docs note: when the viewport changes which image is the LCP element, preferfetchPriority="high"overpreloadso you don’t preload an image that’s hidden on some breakpoints. -
Configure the
imagesblock. This version sets the three things Next.js 16 cares about:
// next.config.mjs
/** @type {import('next').NextConfig} */
const nextConfig = {
images: {
formats: ['image/avif', 'image/webp'], // AVIF is opt-in; default is webp only
qualities: [50, 75, 90], // required in Next.js 16 — quality prop must match
deviceSizes: [640, 750, 828, 1080, 1200, 1920, 2048, 3840],
imageSizes: [32, 48, 64, 96, 128, 256, 384],
minimumCacheTTL: 2678400, // 31 days; default is now 14400 (4 hours)
remotePatterns: [
{ protocol: 'https', hostname: 'images.unsplash.com', pathname: '/photos/**' },
{ protocol: 'https', hostname: 'cdn.yourdomain.com', pathname: '/**' },
],
},
};
export default nextConfig;
A quality prop that isn’t in your qualities array snaps to the nearest allowed value in the component, but a direct hit to the optimizer API with a disallowed quality returns 400. Keep the list short and intentional.
- Remote images need specific
remotePatterns. Avoid bare**for hostname. The Next.js 16 docs warn that omittingprotocol,port,pathname, orsearchimplies a wildcard, which lets attackers route arbitrary images through your optimizer. You can also use the shorthandnew URL('https://example.com/account123/**'). For your own/publicimages, lock them down withlocalPatterns:
images: {
localPatterns: [
{ pathname: '/assets/images/**', search: '' },
],
},
sizesdecides which width gets fetched. Without it, Next.js generates only a1x/2xsrcset; with it, you get the full responsive set (640w,750w, …) and the browser picks the right one. Sample for a typical article hero (full width on mobile, ~800px on desktop):
<Image
src={heroImg}
alt="..."
sizes="(max-width: 640px) 100vw,
(max-width: 1024px) 80vw,
800px"
preload
/>
fillneeds a positioned parent (relative,absolute, orfixed), or the image collapses to zero height. Use it for unknown aspect ratios:
<div style={{ position: 'relative', aspectRatio: '16/9' }}>
<Image src="/cover.jpg" alt="..." fill style={{ objectFit: 'cover' }} sizes="100vw" />
</div>
- Non-Vercel host? Pick a loader strategy. On Vercel the optimizer runs at the edge; elsewhere you run a Node server (request-time optimization), a custom loader, or pre-built assets. A custom loader hands optimization to Cloudflare Images, Imgix, or your own proxy:
const nextConfig = {
images: {
loader: 'custom',
loaderFile: './lib/image-loader.ts',
},
};
// lib/image-loader.ts
export default function loader({ src, width, quality }) {
return `https://imgproxy.yourdomain.com/${width}/q${quality ?? 75}${src}`;
}
For a fully static export, you give up the built-in optimizer for portability:
const nextConfig = {
output: 'export',
images: { unoptimized: true },
};
- Verify with the DevTools Network tab. The hero should fire once with an
image/avifcontent-type and a preload link in<head>. You can also check from the terminal:
curl -sI 'https://yourdomain.com/_next/image?url=/hero.png&w=1080&q=75' \
| grep -iE 'content-type|cache-control'
# content-type: image/avif
# cache-control: public, max-age=2678400
- Measure the LCP impact. Lighthouse points at the actual largest paint element:
npx lighthouse https://yourdomain.com/articles/foo/ \
--only-categories=performance --chrome-flags=--headless --quiet \
| grep -A1 'largest-contentful-paint\|LCP element'
AVIF vs WebP: the real trade-off
The Next.js docs are blunt about it: AVIF takes about 50% longer to encode but compresses roughly 20% smaller than WebP. Two consequences for a content site:
- The first request for an image is slower (the optimizer encodes on demand); every cached request after is faster and lighter.
- Each format is cached separately, so enabling both AVIF and WebP roughly doubles your optimized-image storage. With the new
maximumDiskCacheSizeLRU eviction, that’s usually fine — but on a tiny VPS, set a cap or stick to WebP.
For photographic hero images on a blog, AVIF is worth it. For UI screenshots with flat color, the WebP-only default is often indistinguishable and encodes faster.
Common pitfalls
- Still using
priorityafter upgrading to Next.js 16. It works but is deprecated; migrate topreload. - Forgetting
qualitiesin config, then getting400 Bad Requestfrom the optimizer on an unlisted quality value. - Leaving
minimumCacheTTLat the new 4-hour default on a site with stable, content-hashed images — you re-optimize far more than needed. - Whitelisting
**for hostname inremotePatterns— every random host becomes an optimization target, raising cost and attack surface. - Using
fillwithout a positioned parent — image collapses to zero height. - Omitting
sizesand shipping a 2000px image to a 400px phone column. - Disabling optimization site-wide (
unoptimized: true) to “fix” one bad image — you throw away the whole point ofnext/image.
Who this is for
Any Next.js site shipping images — blogs, marketing pages, product galleries, doc sites with diagrams — especially anyone who just upgraded to Next.js 16 and hasn’t audited their images config.
FAQ
- Is
priorityremoved in Next.js 16?: No, it’s deprecated, not removed — it still works but logs a warning. Replace it withpreloadfor the LCP image, orfetchPriority="high"when the LCP element varies by viewport. - Why am I getting a 400 from the image optimizer after upgrading?: Next.js 16 made
qualitiesrequired. If your<Image quality={...}>value isn’t in the allowed list (default[75]), a direct optimizer request returns400. Add the values you use toimages.qualities. - Does
next/imagework outside Vercel?: Yes. With a Node server it optimizes at request time; with a custom loader it offloads to Cloudflare Images or Imgix. A static export (output: 'export') needs an external loader orunoptimized: true. - Should I enable AVIF?: For photographic content, yes — it’s about 20% smaller than WebP. It encodes ~50% slower on the first request and doubles cache storage, so for flat UI screenshots the WebP default is often enough.
- What about SVGs?:
next/imagewon’t optimize SVG and disables it by default for security. If you must, setdangerouslyAllowSVGplus a strictcontentSecurityPolicy; otherwise inline the SVG or use a plain<img>.
Related
- Next.js Content-Site SEO: The Things That Bite
- Static or SSR: How to Pick for a Content Site
- Deploying a Next.js Site to Vercel
- Sitemap and robots.txt Basics in Next.js
Tags: #Indie dev #Next.js #Core Web Vitals #Technical SEO #Getting started