Next.js Image Optimization in Depth: sizes, Priority, and LCP
AI generated
</>
{ }
React · Next.js · Image Optimization · Core Web Vitals
Next.js Image Optimization in Depth
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.

18 min read sizes · Priority · Remote Patterns · Loader Next.js 14/15

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.

11. FAQ: Next.js Image Optimization

1Why width/height required?
Reserves space before loading, prevents layout shifts, improves CLS.
2What does sizes do?
Describes the actual display width, so the browser picks the right srcset variant.
3When to set priority?
Only on the likely LCP element, usually the hero image in the visible area.
4Why remotePatterns?
Prevents abuse of the optimization API as an open proxy for arbitrary domains.
5How does placeholder=blur work?
Base64 preview inline in HTML, no extra request, until the real image loads.
6When is a custom loader needed?
With an existing image CDN and its own transformation logic, to avoid duplication.
7Are GIFs optimized?
No, passed through unchanged. WebP animation or video is usually the better alternative.
8Also for CSS background images?
No, background-image sits outside the control of next/image.
9Extra server time?
Only on the first request per size/format combination, then cached.
10Missing sizes attribute?
Browser conservatively picks the largest variant, wasting bandwidth on mobile devices.