Fixing Images in Next.js
The Next.js Image component resizes, converts and lazy-loads images for you, so it is easy to assume a Next.js site has no image problems. In practice the component only does well with the right information.
Without a sizes prop it can send desktop-sized files to phones, the main image is lazy unless you say otherwise, AVIF is off by default, and remote images need to be allowed in configuration. This guide covers the component and next.config settings that fix each Image Optimisation finding on a Next.js site, and what changes when you export statically or host it yourself.
Most Next.js image issues come from next/image settings rather than the images themselves.
What next/image does and does not do for you
When you render <Image> from next/image, Next.js outputs an ordinary
img element whose src and srcset point to its optimisation endpoint,
/_next/image, with the original address, a width and a quality in the query string. The
first request for each combination is resized and re-encoded on the server, then cached. The component
also adds loading="lazy" and decoding="async" by default and requires dimensions
so space is reserved.
What it cannot know is how your layout uses the image. It does not read your CSS, so it cannot tell
that a card is 300 pixels wide on desktop. That is the job of the sizes prop. It also cannot
tell which image is the most important, so it lazy-loads all of them until you mark one as the priority.
| Audit finding | Usual Next.js cause | Fix |
|---|---|---|
| Oversized dimensions | No sizes on a responsive or fill image | Add
a sizes prop matching the layout |
| Main image lazy-loaded | Default lazy loading left on the hero | Mark it for early loading |
| Old format | Only WebP enabled, or images bypass the component | Add AVIF to images.formats;
use the component |
| Oversized file | Raw img tags or CSS backgrounds pointing at originals | Move
to next/image or pre-size the files |
| Slow or uncached delivery | Self-hosted optimiser without persistent cache, or a short cache lifetime | Persist the image cache or use a loader with a CDN |
The general points about srcset, lazy loading and CDNs are covered in the sibling guides
on image dimensions, lazy
loading and CDN delivery. This page
is about how those map onto Next.js.
The fix, step by step
Step 1
Find images that bypass the component
Search
the project for raw <img tags, backgroundImage in styles, and images inserted
from a CMS as HTML strings. None of these are resized or converted by Next.js. Run the Image Optimisation
audit on a few routes and compare its list with what you find. Content images in your own components
should move to next/image. Images inside CMS rich text can be mapped to the component with
a custom renderer, or served already sized from the CMS image service.
Step 2
Allow remote sources with remotePatterns
Images
from another host, such as a headless CMS or storage bucket, fail with an error until that host is permitted.
In next.config, add an entry to images.remotePatterns with the protocol, hostname
and, ideally, a pathname prefix such as /uploads/**. Keep patterns as narrow as you can,
because anything allowed can be pulled through your optimiser at your expense. Replace the older images.domains
setting if your project still uses it, as it is deprecated.
Step 3
Give every image its dimensions
Statically
imported images, written as import hero from './hero.jpg', get their width, height and a
blur placeholder automatically. Remote images need explicit width and height
props giving the original's aspect ratio. When you do not know the size in advance, for instance user
uploads, use the fill prop instead and place the image inside a parent with position: relative
and a set height or aspect ratio, using a CSS class rather than inline styles.
Step 4
Add a sizes prop to responsive and fill images
Without sizes, an image with fixed width gets only 1x and 2x variants,
and a fill image is assumed to be as wide as the viewport, so a small card can receive a
file sized for a large desktop screen. Give every image that changes size with the layout a sizes
prop that mirrors your CSS breakpoints, such as (min-width: 1024px) 33vw, (min-width: 640px) 50vw, 100vw.
This single prop fixes most oversized findings on Next.js sites.
Step 5
Load the main image early
Mark
the one image that is usually the Largest Contentful Paint element, typically the hero or first product
photo, so it is not lazy-loaded. In Next.js 16 the priority prop is deprecated in favour
of preload, and for most cases the documentation suggests loading="eager" or
fetchPriority="high" instead. On earlier versions use priority. Do it on one
image per route, not every image, and pass the prop down from the page so shared components are not always
eager.
Step 6
Turn on AVIF and tune widths and quality
By default the optimiser produces WebP only. Set images.formats to ['image/avif', 'image/webp']
so browsers that accept AVIF receive it first. AVIF takes longer to encode, so the first request for
each variant is slower, but later ones are cached. Review deviceSizes so the widths match
your real breakpoints, and list the quality values you use in images.qualities, which recent
versions require for any quality other than the default.
Step 7
Choose where optimisation runs
On
Vercel, the platform runs the optimiser and its usage counts against your plan, so check the usage page
after enabling AVIF or adding widths. When self-hosting with next start, the server does
the work and caches results under .next/cache/images, which must survive deployments. With
output: 'export' there is no server, so set images.unoptimized to true and
pre-size images, or configure a custom loader that builds URLs for an image CDN.
Step 8
Check the rendered HTML and cache headers
View the page source rather than the React tree, and confirm each important image has
a sensible srcset, a sizes attribute, and that the hero has no loading="lazy".
In the Network panel, check that image responses come back as AVIF or WebP, at widths near the slot size,
and with a long cache-control on repeat requests. Test at phone and desktop widths, then
run the Image Optimisation audit again on the same routes.
Configuration and component examples
next.config.js
/** @type {import('next').NextConfig} */
module.exports = {
images: {
formats: ['image/avif', 'image/webp'],
remotePatterns: [
{ protocol: 'https', hostname: 'cms.example.com', pathname: '/uploads/**' },
],
deviceSizes: [640, 828, 1080, 1280, 1920],
qualities: [60, 75],
},
};
Keep the lists short. Every extra width and quality multiplies the number of variants the optimiser may have to produce and cache for each image.
A hero image on a route
import Image from 'next/image';
import hero from '@/public/img/fellside-hero.jpg';
export default function Home() {
return (
<Image
src={hero}
alt="Walkers on a ridge above Buttermere in autumn"
sizes="100vw"
className="hero-img"
fetchPriority="high"
loading="eager"
placeholder="blur"
/>
);
}
The static import supplies width, height and a blur placeholder. On versions before 16, replace the
last two loading props with priority.
A product card using fill
// ProductCard.jsx
<div className="card-media">
<Image
src={product.imageUrl}
alt={product.imageAlt}
fill
sizes="(min-width: 1024px) 25vw, (min-width: 640px) 50vw, 100vw"
className="card-img"
/>
</div>
/* styles.css */
.card-media { position: relative; aspect-ratio: 4 / 5; }
.card-img { object-fit: cover; }
A custom loader for an image CDN
// image-loader.js
export default function loader({ src, width, quality }) {
const q = quality || 75;
return `https://img.example.com${src}?w=${width}&q=${q}&auto=format`;
}
// next.config.js
module.exports = {
images: { loader: 'custom', loaderFile: './image-loader.js' },
};
With a custom loader, Next.js still builds the srcset from your configured widths, but
each URL points at the CDN, which does the resizing. This is the usual answer for static export and for
sites that already pay for an image service.
Hosting differences that affect images
Vercel
Image optimisation is built in and cached at the edge. It is metered, so a site with many remote images,
many widths and AVIF enabled uses more than one with a few static images. Narrow remotePatterns
and short width lists keep usage predictable. Check the current plan limits in your dashboard rather
than assuming.
Self-hosted Node server or container
Recent versions of Next.js use the Sharp library automatically for next start and standalone
output. The optimised files are written to .next/cache/images. In containers or on platforms
that rebuild the file system on every deployment, that cache is lost, so each variant is regenerated
on the first visit after each release. Mount a persistent volume for the cache, or put a CDN in front
that respects the long cache headers the optimiser sends.
The images.minimumCacheTTL setting controls how long optimised images are cached when
the original has no longer cache header of its own. Next.js 16 raised its default considerably; on older
versions it was short enough to cause frequent re-optimisation, so set it explicitly if you are on an
older release.
Static export
With output: 'export' the site is plain files with no server to run the optimiser. The
build fails for images using the default loader unless you set images.unoptimized to true
or configure a custom loader. With unoptimized, the component still reserves space and lazy-loads,
but serves your files exactly as they are, so they must already be sized and converted.
unoptimized to fix a build error, and then forgetting about
it, is a common reason a Next.js site suddenly fails image findings. If you see original-sized JPEGs
in the Network panel, check for this setting first.Edge cases
- SVG files. Vector images gain nothing from the optimiser. Pass
unoptimizedfor SVGs, or use a plainimg. ThedangerouslyAllowSVGsetting exists but carries security implications for user-supplied files. - Animated GIFs. Animated images are not resized; they are passed through. Convert long GIFs to short muted videos instead.
- CSS background images.
next/imagecannot help with these. For a decorative background use CSS with a pre-sized file; for an important one, use afillimage withobject-fit: coverbehind the content. - Images from a CMS with its own resizer. If the CMS already serves sized, converted images, running them through the Next.js optimiser as well doubles the work. A custom loader that uses the CMS's own parameters is often simpler.
- Local images with query strings. Recent versions require
images.localPatternsto permit query strings on local image paths. If a local image suddenly stops loading after an upgrade, check this. - Pages Router projects. The same component and settings apply. Older projects may
still use
next/legacy/image, which wraps images in extra elements; migrating to the current component simplifies styling.
Keeping a Next.js site image-healthy
- Wrap the component. Build a small site-specific image component that requires
sizesand a priority flag, so developers cannot forget them. - Lint for raw img tags. The Next.js ESLint configuration warns about plain
imgelements. Keep that rule on and justify exceptions in code. - Review config on upgrades. Image defaults and required settings have changed between major versions. Read the image section of the upgrade guide when moving to a new major release.
- Measure after each release. Run the Image Optimisation audit on your key routes and compare Largest Contentful Paint in field data. For the wider framework picture, see our Core Web Vitals in Next.js guide.
1. Should I use next/image for every image in a Next.js site?
For content images such as heroes, product photos and article images, yes, because it handles resizing, modern formats, lazy loading and reserved space in one place. There are sensible exceptions: SVG logos and icons, which do not need optimisation; images already served at the right size by a CMS or CDN, which can use a custom loader; and purely decorative backgrounds, which belong in CSS. Keep the ESLint warning on so exceptions are deliberate.
2. Why does next/image send such large images to phones?
Almost always because the sizes prop is missing. Without it, an image with a fixed width only gets one and two times density variants, and a fill image is assumed to span the full viewport. The browser then picks a file sized for that assumption. Add a sizes prop that describes how wide the image is at each breakpoint, and check the chosen file in the Network panel at a phone width afterwards.
3. What happened to the priority prop in Next.js 16?
Next.js 16 deprecated the priority prop on the Image component and introduced preload to take its place. The documentation suggests that in most cases you should instead set loading to eager or fetchPriority to high on the main image, which avoids adding a separate preload tag. On Next.js 15 and earlier, priority is still the way to mark the hero. Whichever you use, apply it to one image per route.
4. Why am I getting an error that the hostname is not configured?
Next.js refuses to optimise images from hosts you have not allowed, to stop anyone using your server to resize arbitrary images. Add the host to images.remotePatterns in next.config with its protocol, hostname and ideally a pathname prefix, then restart the development server. Avoid wildcards that allow any host. If the images come from a service that already resizes them, a custom loader may be a better choice than allowing the host.
5. How do I use next/image with static export?
Static export produces plain files with no server to run the optimiser, so the default loader cannot work. You have two options. Set images.unoptimized to true and make sure every image is already resized and converted before the build, or configure a custom loader that points image URLs at an image CDN which resizes on request. The component still reserves space, applies lazy loading and builds srcset with either approach.
6. Why are images slow the first time after each deployment?
The optimiser creates each width and format the first time it is requested and caches the result. On self-hosted setups the cache lives in the .next/cache/images folder, and if that folder is wiped on each deployment, every variant must be generated again. Enabling AVIF makes those first requests slower still. Keep the cache on a persistent volume or put a CDN in front, and consider warming key pages after release.
7. Does enabling AVIF in Next.js have any downsides?
AVIF files are usually smaller than WebP at similar quality, but encoding them takes more processing time. The first request for each variant is therefore slower, and on metered platforms the extra work may count against your usage. Once cached, delivery is fast. For most sites the smaller files are worth it; if first-request speed or usage limits matter a lot, keep WebP only or pre-generate AVIF files at build time.
The Image Optimisation audit checks every image on your page for file size, format, dimensions, alt text, lazy-loading, layout shift and CDN delivery, and ranks its recommendations by their effect on loading and bandwidth.
Open Image Optimisation Fixes โ