next/image: Image Optimisation Done Right
The Image component can transform your Core Web Vitals or quietly sabotage them. Here is how to size, prioritise and configure images properly in Next.js.
Images are usually the heaviest thing on a page and the most common cause of a poor Largest Contentful Paint. The next/image component exists to make the right thing automatic: modern formats, responsive sizes, lazy loading and zero layout shift. But it only delivers if you configure it honestly — and the two attributes teams get wrong are sizes and priority.
What the component gives you
- Automatic AVIF and WebP serving based on browser support
- A generated srcset so phones never download desktop-sized files
- Lazy loading by default for images below the fold
- Enforced dimensions, which eliminates layout shift from images
None of this requires an image pipeline of your own. The optimisation happens on demand the first time a size is requested, then the result is cached.
Sizing: width, height and fill
Every image needs either explicit width and height or the fill prop. The numbers describe the intrinsic aspect ratio, not the rendered size — CSS still controls layout. Use fill when the image should adopt the dimensions of a positioned parent, such as a card thumbnail or a hero banner.
import Image from 'next/image';
export function Hero() {
return (
<div className="relative h-96">
<Image
src="/hero.jpg"
alt="Team at work in the studio"
fill
priority
sizes="100vw"
className="object-cover"
/>
</div>
);
}The sizes attribute is not optional
Without sizes, the browser assumes the image spans the full viewport and picks a source accordingly. A 300-pixel-wide card thumbnail on a large monitor can end up downloading a 1920-pixel file — technically optimised, practically wasteful. Tell the browser the truth: something like (max-width: 768px) 100vw, 33vw for a three-column grid. It is one line and routinely halves image transfer on listing pages. With fill, an accurate sizes matters doubly, because the browser has no width hints at all without it.
LCP and the priority prop
Lazy loading is the right default for everything except the image that is your LCP element. That one should never wait: add priority so it is preloaded and fetched eagerly. There should usually be exactly one priority image per page — the hero, the product photo, whatever dominates the first viewport. Next.js logs a development warning when it detects an LCP image without priority; treat that warning as a bug.
Placeholders and static imports
For images you ship with the app, import the file rather than referencing a path string. Static imports give Next.js the dimensions at build time, so width and height come for free, and adding placeholder set to blur enables an automatic blurred preview while the full image loads. The blur-up effect makes slow connections feel deliberate rather than broken.
Remote images can have placeholders too, but you must supply the blurDataURL yourself — most CMSs and image services will generate a tiny base64 preview alongside the asset. It is a small touch that noticeably improves perceived quality on image-heavy pages.
Remote images
Images from a CMS or object storage must be allow-listed, which prevents your optimiser being used as an open proxy. Be as specific as the source allows.
// next.config.ts
const nextConfig = {
images: {
remotePatterns: [
{
protocol: 'https',
hostname: 'images.ctfassets.net',
pathname: '/your-space-id/**',
},
],
},
};One operational note: if you self-host, image optimisation runs on your server and sharp does the work. Budget CPU for it, or put a CDN in front of the optimised URLs so each variant is transformed once. Raising minimumCacheTTL is worthwhile for assets that never change once published.
Get sizes and priority right on your key templates and you have done most of what matters. Then make a habit of glancing at the network tab on a listing page every so often — image regressions are silent, and one misconfigured template can undo the lot.