sizes, priority, and the path to a good LCP
The next/image component automatically generates responsive image variants, modern formats, and lazy loading, but it does not solve every performance problem on its own. Configuring sizes correctly, setting priority deliberately on the Largest Contentful Paint element, and maintaining remote patterns cleanly is what actually unlocks the full potential of Next.js image optimization.
Table of Contents
- 1. Why image optimization determines performance
- 2. The next/image component: how it works in detail
- 3. Configuring responsive images with sizes and srcset correctly
- 4. Priority, lazy loading, and Largest Contentful Paint
- 5. Configuring external images and remote patterns
- 6. Custom image loader for CDN integration
- 7. Placeholder strategies: blur and shimmer
- 8. Limits of next/image optimization
- 9. next/image compared to classic img
- 10. Summary
- 11. FAQ
1. Why image optimization determines performance
Images are the largest single resource on most web pages and therefore directly influence the most important Core Web Vitals metric, Largest Contentful Paint. Without systematic Next.js image optimization, the browser often loads an image at full resolution even when it is only displayed as a small preview on a mobile device, wasting bandwidth and unnecessarily extending perceived load time. The next/image component was built exactly for this problem: automatic size adjustment, modern image formats, and built-in lazy loading, without developers having to implement every optimization manually.
The core of Next.js image optimization is a server-side optimization process that generates an image in the right size and format on the first request and then caches it. Instead of a single, always identically sized image, Next.js delivers a matching variant depending on the viewport width and pixel density of the respective device, generated through an internal image optimization API that is handled either by Next.js itself or by a configured external loader.
Important to understand: Next.js image optimization is not a set-and-forget feature. The component provides the technical infrastructure, but a misconfigured sizes attribute, missing priority on the LCP element, or careless remote pattern configuration can negate the expected performance gain. The following sections go through the most important configuration decisions in detail.
2. The next/image component: how it works in detail
The next/image component replaces the classic <img> tag with a component that requires either width and height, or alternatively the fill prop. This requirement is not a bureaucratic detail, it prevents layout shifts: the browser reserves the correct space for the image before it is even loaded, which directly improves the Cumulative Layout Shift metric, one of the three central Core Web Vitals figures.
When rendering, Next.js image optimization automatically generates a srcset attribute with several image variants at different widths, defined via the deviceSizes and imageSizes configuration in next.config.js. The browser independently picks the matching variant from the srcset based on the actual display size and pixel density, with no additional JavaScript code at runtime. This mechanism is a web standard and works regardless of React.
// components/ProductHero.tsx — basic next/image usage with explicit dimensions
import Image from 'next/image';
export function ProductHero({ product }: { product: { imageUrl: string; name: string } }) {
return (
<Image
src={product.imageUrl}
alt={product.name}
width={1200}
height={630}
priority
className="rounded-xl object-cover"
/>
);
}
3. Configuring responsive images with sizes and srcset correctly
The sizes prop is the most commonly misunderstood part of Next.js image optimization. It does not describe the actual image size, it tells the browser in advance how wide the image will actually be displayed in the layout at different viewport widths, so it can choose the right variant from the srcset even before the full CSS render completes. If sizes is missing on a responsive image, the browser conservatively picks the largest available variant, wasting unnecessary bandwidth.
A typical pattern for an image that takes up a third of the width on large screens, half on tablets, and full width on mobile devices looks like this: sizes="(max-width: 640px) 100vw, (max-width: 1024px) 50vw, 33vw". Each rule is checked from left to right, the first matching media query determines the width. A common mistake is leaving this attribute copied over even though the actual CSS layout of an image in the project has changed, leading to a silent discrepancy between the declared and actual display size.
// components/ProductGrid.tsx — sizes matches the actual responsive layout
import Image from 'next/image';
export function ProductCard({ product }: { product: { imageUrl: string; name: string } }) {
return (
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4">
<Image
src={product.imageUrl}
alt={product.name}
width={800}
height={800}
// Full width on mobile, half on tablet, a third on desktop grid
sizes="(max-width: 640px) 100vw, (max-width: 1024px) 50vw, 33vw"
className="rounded-lg object-cover w-full h-auto"
/>
</div>
);
}
4. Priority, lazy loading, and Largest Contentful Paint
By default, next/image loads every image with loading="lazy", which makes sense for most images below the fold, since it saves bandwidth until the user actually scrolls. For the image that is likely the page's Largest Contentful Paint element, usually a hero image or a large product photo directly in the visible area, this exact lazy loading is counterproductive: it delays loading an image the user sees immediately.
The priority prop turns off lazy loading for exactly that image and additionally instructs Next.js to add a <link rel="preload"> hint to the <head>, so the browser requests the image already during initial HTML parsing instead of only after full CSS and JavaScript have loaded. This combination of no lazy loading and a preload hint is the single most important lever for actually connecting Next.js image optimization to a good LCP time. A common mistake is accidentally setting priority on several images, which dilutes the preload mechanism and no longer prioritizes loading the actually relevant resource.
5. Configuring external images and remote patterns
For security reasons, next/image does not optimize images from arbitrary external domains by default. Every domain from which images are loaded via a URL rather than a local import must be explicitly allowed through remotePatterns in next.config.js. This restriction prevents Next.js's internal image optimization API from being abused as an open proxy for arbitrary image URLs, a real security risk that would exist without this control.
A typical setup for an e-commerce shop with a separate media server or CDN defines exactly the needed hostnames and path patterns, instead of blanket-allowing all HTTPS domains. Important: changes to remotePatterns require a rebuild of the application, because the configuration is evaluated at build time. A common production mistake happens when a new image host, say after a CDN switch, gets forgotten in the configuration, and images then get rejected with a 400 error from the internal optimization API.
// next.config.js — explicit remote patterns for external image domains
/** @type {import('next').NextConfig} */
const nextConfig = {
images: {
remotePatterns: [
{
protocol: 'https',
hostname: 'cdn.mironsoft-media.de',
pathname: '/products/**',
},
{
protocol: 'https',
hostname: 'images.unsplash.com',
},
],
deviceSizes: [640, 750, 828, 1080, 1200, 1920],
imageSizes: [16, 32, 48, 64, 96, 128, 256],
},
};
module.exports = nextConfig;
6. Custom image loader for CDN integration
Anyone already running a specialized image CDN service like Cloudinary, Imgix, or their own image optimization infrastructure can completely bypass Next.js's built-in image optimization API and define a custom loader instead. This function receives src, width, and quality as parameters and must return a complete URL pointing to the correspondingly transformed image variant at the external service. The rest of the Next.js image optimization logic, meaning srcset generation, lazy loading, and priority handling, remains fully intact.
A custom loader pays off especially when the existing CDN service already has its own caching infrastructure, image transformations, and global distribution, so the additional Next.js-native optimization layer would be redundant or even lead to duplicate processing. For most projects without an existing image CDN contract, however, the built-in Next.js optimizer remains the simpler and sufficiently performant choice.
// lib/cloudinary-loader.ts — custom loader delegating optimization to Cloudinary
export default function cloudinaryLoader({
src,
width,
quality,
}: {
src: string;
width: number;
quality?: number;
}) {
const params = [`w_${width}`, `q_${quality ?? 75}`, 'f_auto', 'c_limit'];
return `https://res.cloudinary.com/mironsoft/image/upload/${params.join(',')}${src}`;
}
// next.config.js
// images: { loader: 'custom', loaderFile: './lib/cloudinary-loader.ts' }
7. Placeholder strategies: blur and shimmer
An empty, white area while an image loads looks unfinished and can look like an error on slow connections. The placeholder="blur" option of Next.js image optimization instead shows a heavily downscaled, blurry preview delivered inline as a base64-encoded image directly in the HTML, with no additional network request. For locally imported images, Next.js generates this blur placeholder automatically at build time; for externally loaded images, blurDataURL must be supplied manually, for example from a thumbnail generated at upload time.
Alternatively, a shimmer effect, an animated gradient placeholder, can be realized through a custom blurDataURL with an SVG data URI, which delivers a more consistent visual result than a generic gray area, especially for images without a meaningful blur preview, say pure text graphics or icons. In both cases, it is important that the placeholder itself stays extremely lightweight, usually well under a kilobyte, so it does not create its own performance problem.
8. Limits of next/image optimization
Next.js image optimization excellently solves the problem of wrong image sizes and missing modern formats, but not every image problem. Animated GIFs are not transformed in their animation by the optimization API by default, they are passed through unchanged, which can still cause performance problems with very large GIF files. For animated content, switching to WebP animations or video elements with autoplay muted loop is often the better alternative to a classic GIF.
A second limit concerns CSS background images, which sit outside the control of the next/image component. A CSS image set via background-image does not automatically benefit from responsive sizing or modern formats; here you either need to work manually with image-set() or move the image logic into an actual <Image> component with fill and matching object-fit.
9. next/image compared to classic img
The following table places the built-in Next.js image optimization against the classic <img> tag.
| Aspect | Classic img | next/image | Advantage |
|---|---|---|---|
| Responsive variants | Manual with srcset | Automatically generated | No manual upkeep of multiple files |
| Modern formats | Manual conversion needed | AVIF/WebP automatic | Smaller file sizes with no extra work |
| Layout shift protection | Only with manual width/height | Enforced by requirement | Better CLS scores |
| Lazy loading | loading="lazy" set manually | Default behavior | Less boilerplate |
| External domains | No restriction | remotePatterns needed | img simpler for uncontrolled external sources |
The table shows: Next.js image optimization wins on almost every criterion, with the exception of the extra configuration requirement for external domains, which exists for good security reasons and is a one-time setup with little effort.
Mironsoft
Next.js performance and Core Web Vitals optimization
Images that load fast instead of hurting your LCP?
We configure next/image with correct sizes attributes, deliberate priority usage on the LCP element, and, where it makes sense, a custom loader for your existing CDN infrastructure.
LCP analysis
Identifying the LCP element and correct priority configuration
sizes audit
Matching sizes attributes to the actual responsive layout
CDN integration
Custom image loader for Cloudinary, Imgix, or existing media infrastructure
10. Summary
Next.js image optimization automates responsive image sizes, modern formats, and layout shift protection through the next/image component, but it does not solve every detail without deliberate configuration. A correctly set sizes attribute prevents unnecessarily large downloads, priority on the actual LCP element directly improves the most important Core Web Vitals metric, and cleanly maintained remotePatterns prevent security gaps with externally hosted images.
For projects with existing CDN infrastructure, a custom loader offers the ability to combine the benefits of the next/image API with an already established image pipeline. Anyone who makes these configuration decisions deliberately, instead of blindly relying on the defaults, gets genuinely measurable improvements in load time and Core Web Vitals out of Next.js image optimization.
Next.js Image Optimization: The Essentials at a Glance
Set sizes correctly
Describes the actual display width in the layout, prevents unnecessarily large downloads.
Priority for LCP
Disables lazy loading and sets a preload hint for the visible main image, directly improving LCP.
Remote patterns
Explicit allowlisting of external image domains, prevents abuse of the optimization API as an open proxy.
Custom loader
Makes sense with existing CDN infrastructure, otherwise the built-in optimizer remains the simpler choice.