Master Next.js image optimization with the next/image component. Learn layout modes, priority prop, sizes attribute, lazy loading, AVIF/WebP, and how to boost Core Web Vitals. This practical guide covers step-by-step setup, real-world code snippets, security hardening, and common production edge cases.
Images routinely account for the largest share of page weight on the web. According to the HTTP Archive, images make up over 50% of the average page's total bytes — and poorly handled images are one of the most common culprits behind slow Largest Contentful Paint (LCP) scores, layout shifts, and frustrated users. Next.js ships a first-class solution to this problem through its built-in next/image component, which automates much of the heavy lifting that developers traditionally had to handle manually.
The next/image component is not simply a wrapper around the native <img> tag. Under the hood it connects to Next.js's built-in Image Optimization API, which resizes, compresses, and converts images to modern formats such as WebP and AVIF on demand and caches the results at the CDN edge. This means you can ship a single high-resolution source image and let the framework serve the right size and format to every browser automatically — without any build-time preprocessing pipeline or third-party image CDN subscription.
This deep dive covers everything you need to confidently use next/image in production Next.js applications: layout modes, the priority prop, the sizes attribute, lazy loading, AVIF/WebP support, placeholder blur, remote image patterns, and their combined impact on Core Web Vitals. All examples use TypeScript and Next.js 14+.
Prerequisites
Before working through this guide, you should be comfortable with:
- Next.js 13+ App Router or Pages Router — examples are compatible with both.
- TypeScript basics (interfaces, generic types, props typing).
- A working Next.js project (
npx create-next-app@latest --typescript). - Basic familiarity with Core Web Vitals: LCP, CLS, and FID/INP.
Section 1: The next/image Component Basics
The simplest possible usage of next/image replaces a raw <img> tag with the Image component imported from next/image. For local images you can import the file directly; TypeScript and the Next.js bundler will infer width and height automatically from the imported asset.
// app/components/HeroBanner.tsx
import Image from "next/image";
import heroImg from "@/public/images/hero.jpg"; // local import – size inferred
export default function HeroBanner() {
return (
<section className="relative w-full h-[480px]">
<Image
src={heroImg}
alt="Developer working on a laptop in a modern office"
placeholder="blur" // uses the auto-generated blur data URL
priority // preload – above the fold
className="object-cover"
fill
/>
</section>
);
}
For remote images (a URL string), you must supply width and height explicitly because Next.js cannot introspect the dimensions at build time:
// app/components/ArticleCard.tsx
import Image from "next/image";
interface ArticleCardProps {
title: string;
coverUrl: string;
}
export default function ArticleCard({ title, coverUrl }: ArticleCardProps) {
return (
<article className="rounded-lg overflow-hidden shadow-md">
<Image
src={coverUrl}
alt={`Cover image for article: ${title}`}
width={800}
height={450}
sizes="(max-width: 768px) 100vw, 50vw"
className="w-full h-auto"
/>
<h2 className="p-4 text-xl font-semibold">{title}</h2>
</article>
);
}
The component automatically:
- Generates a
srcsetwith multiple resized variants. - Converts the image to WebP (or AVIF if the browser supports it).
- Injects
loading="lazy"by default for below-the-fold images. - Reserves the exact pixel space needed to prevent Cumulative Layout Shift.
Section 2: Layout Modes — fill, responsive, fixed, intrinsic
Prior to Next.js 13, the layout prop controlled sizing behavior. In Next.js 13+ this has been replaced by a more composable CSS-first approach using the fill boolean prop combined with sizes and Tailwind (or plain CSS) for responsive behavior. Understanding the four historical layout modes and their modern equivalents is important when upgrading legacy projects.
Mode (legacy layout prop) | Modern Equivalent | Use Case |
|---|---|---|
layout="fill" | fill prop + position: relative parent | Hero banners, background images |
layout="responsive" | No fill, explicit width/height + w-full h-auto CSS | Blog post images, article covers |
layout="fixed" | Explicit width and height, no fluid CSS | Avatars, icons, logos with fixed px size |
layout="intrinsic" | Explicit width/height + max-w-full CSS | Images that scale down but never up |
Fill mode — used when the image should cover a positioned container:
// Requires the parent to have position: relative (or absolute/fixed) and explicit dimensions
<div className="relative w-full h-64">
<Image
src="/images/landscape.webp"
alt="Mountain landscape"
fill
sizes="100vw"
className="object-cover object-center"
/>
</div>
Responsive (fluid) mode — the image scales with its container:
<Image
src="/images/diagram.png"
alt="Architecture diagram"
width={1200}
height={630}
sizes="(max-width: 640px) 100vw, (max-width: 1024px) 80vw, 1200px"
className="w-full h-auto"
/>
Fixed mode — useful for user avatars or brand logos:
<Image
src={user.avatarUrl}
alt={`${user.name} avatar`}
width={48}
height={48}
className="rounded-full"
/>
Section 3: The priority Prop and LCP Optimization
The priority prop is the single most impactful next/image option for Largest Contentful Paint performance. When set to true, Next.js injects a <link rel="preload"> tag for the image in the document <head> and removes the default loading="lazy" attribute. This means the browser starts downloading the image immediately during resource discovery — before the JavaScript bundle even executes.
Rule of thumb: add priority to every image that is visible in the initial viewport on desktop and mobile — typically hero images, above-the-fold banners, and the first article card in a grid.
// app/page.tsx – Server Component
import Image from "next/image";
export default function HomePage() {
return (
<main>
{/* LCP candidate – must use priority */}
<div className="relative w-full h-[560px]">
<Image
src="https://images.pexels.com/photos/270408/pexels-photo-270408.jpeg"
alt="Hero image for homepage"
fill
priority // ← preloads the image; removes lazy loading
sizes="100vw"
className="object-cover"
/>
</div>
{/* Below-the-fold grid – lazy loaded by default */}
<section className="grid grid-cols-3 gap-4 p-8">
{posts.map((post) => (
<Image
key={post.id}
src={post.cover}
alt={post.title}
width={400}
height={225}
sizes="(max-width: 768px) 100vw, 33vw"
/>
))}
</section>
</main>
);
}
Without priority on your LCP image, the browser fetches it lazily — after layout — which is almost always too late to achieve a good LCP score.
Section 4: The sizes Attribute for Responsive Images
The sizes attribute tells the browser how wide the image will be rendered at each viewport breakpoint. Next.js uses this information to generate an optimal srcset and to select the right image variant to download. Getting sizes right directly reduces wasted bytes on mobile devices.
// A full-width image on mobile, half-width on tablet, one-third on desktop
<Image
src="/images/product.jpg"
alt="Product photography"
width={1200}
height={800}
sizes="(max-width: 640px) 100vw,
(max-width: 1024px) 50vw,
33vw"
className="w-full h-auto rounded-md"
/>
If you omit sizes when using fill, Next.js defaults to 100vw — meaning it downloads a viewport-wide image even when the element occupies only a fraction of the screen. Always supply an accurate sizes string for fill-mode images inside constrained containers:
<div className="relative w-64 h-64">
<Image
src="/images/thumbnail.jpg"
alt="Product thumbnail"
fill
sizes="256px" // constant 256 px; no need for media queries
className="object-contain"
/>
</div>
Section 5: Lazy Loading and the loading Attribute
By default, every next/image without priority receives loading="lazy", which defers the network request until the image is near the viewport. This is the correct behavior for the vast majority of images. In rare cases — such as a print stylesheet or a server-side PDF renderer — you may want to override this:
// Force eager loading without the full priority preload behaviour
<Image
src="/images/print-header.png"
alt="Document header"
width={800}
height={120}
loading="eager"
/>
Avoid applying loading="eager" to off-screen images; doing so wastes bandwidth and can hurt LCP by competing with your critical resources during initial page load.
Section 6: AVIF and WebP Format Support
Next.js's Image Optimization API serves images in the best format the requesting browser supports. The preference order is:
- AVIF — ~50% smaller than JPEG at the same quality; supported in Chrome 85+, Firefox 93+, Safari 16+.
- WebP — ~30% smaller than JPEG; supported in all modern browsers.
- Original format — fallback for older browsers.
You can customize the format list in next.config.ts:
// next.config.ts
import type { NextConfig } from "next";
const nextConfig: NextConfig = {
images: {
formats: ["image/avif", "image/webp"], // order matters – avif is preferred
minimumCacheTTL: 60 * 60 * 24 * 30, // cache optimized images for 30 days
deviceSizes: [640, 750, 828, 1080, 1200, 1920, 2048, 3840],
imageSizes: [16, 32, 48, 64, 96, 128, 256, 384],
},
};
export default nextConfig;
deviceSizes controls the breakpoints used in the srcset for full-width images (those using sizes values like 100vw). imageSizes controls the breakpoints for constrained images.
Section 7: Placeholder Blur and Blur Data URL
Showing a blurred placeholder while the full image loads prevents jarring layout shifts and gives users immediate visual feedback — a technique often called Low Quality Image Placeholder (LQIP).
For local images, placeholder="blur" works out of the box; Next.js generates the blurDataURL automatically at build time:
import Image from "next/image";
import teamPhoto from "@/public/images/team.jpg";
export default function TeamSection() {
return (
<Image
src={teamPhoto}
alt="Our engineering team"
placeholder="blur" // blurDataURL auto-generated from local file
width={1200}
height={630}
className="w-full h-auto"
/>
);
}
For remote images, you must supply blurDataURL manually. The recommended approach is to generate a tiny base64-encoded placeholder at build time using a utility like plaiceholder:
// lib/image-placeholder.ts
import { getPlaiceholder } from "plaiceholder";
export async function getBlurDataUrl(imageUrl: string): Promise<string> {
const buffer = await fetch(imageUrl).then(async (res) =>
Buffer.from(await res.arrayBuffer())
);
const { base64 } = await getPlaiceholder(buffer);
return base64;
}
// app/blog/[slug]/page.tsx (Server Component)
import Image from "next/image";
import { getBlurDataUrl } from "@/lib/image-placeholder";
interface PageProps {
params: { slug: string };
}
export default async function BlogPostPage({ params }: PageProps) {
const post = await getPostBySlug(params.slug);
const blurDataURL = await getBlurDataUrl(post.coverImage);
return (
<Image
src={post.coverImage}
alt={post.title}
width={1200}
height={630}
placeholder="blur"
blurDataURL={blurDataURL}
priority
className="w-full h-auto rounded-xl"
/>
);
}
Section 8: Remote Patterns Config in next.config.ts
For security reasons, Next.js blocks optimization of remote images from domains not explicitly allowed. The modern remotePatterns configuration (which supersedes the deprecated domains array) uses a glob-style pattern syntax that gives you fine-grained control over which hosts, paths, and protocols are permitted:
// next.config.ts
import type { NextConfig } from "next";
const nextConfig: NextConfig = {
images: {
remotePatterns: [
{
protocol: "https",
hostname: "images.pexels.com",
port: "",
pathname: "/photos/**", // only allow /photos/* paths
},
{
protocol: "https",
hostname: "**.cloudinary.com", // wildcard subdomain
},
{
protocol: "https",
hostname: "res.cloudinary.com",
pathname: "/your-cloud-name/**",
},
{
protocol: "https",
hostname: "avatars.githubusercontent.com",
},
],
formats: ["image/avif", "image/webp"],
},
};
export default nextConfig;
Using ** in hostname matches any number of subdomain segments. Using ** in pathname matches any path depth. This is far more secure than the old domains whitelist, which allowed any path on a given host.
Section 9: Core Web Vitals Impact
Correctly using next/image has a measurable, positive impact on all three image-related Core Web Vitals:
Largest Contentful Paint (LCP) — The priority prop eliminates the lazy-load delay for your hero image. Combined with AVIF/WebP format delivery and a properly sized srcset, you can cut LCP asset download time by 40–60% compared to a naive <img> tag approach.
Cumulative Layout Shift (CLS) — Because next/image requires either explicit width/height props or a fill parent, the browser can always compute the image's aspect ratio before the image loads. This reserves the correct space in the layout and prevents the image-pop layout shift that is responsible for the majority of CLS violations on image-heavy pages.
Interaction to Next Paint (INP) — While not directly tied to image loading, reducing main-thread work from unnecessary image decoding (thanks to efficient formats and correct sizing) reduces jank and keeps the main thread available for user interactions.
// Auditing your LCP image with next/image – checklist component (dev only)
// components/DevImageAudit.tsx
"use client";
import { useEffect } from "react";
export function DevImageAudit() {
useEffect(() => {
if (process.env.NODE_ENV !== "development") return;
const observer = new PerformanceObserver((list) => {
list.getEntries().forEach((entry) => {
if (entry.entryType === "largest-contentful-paint") {
const lcpEntry = entry as LargestContentfulPaint;
if (lcpEntry.element?.tagName === "IMG") {
const img = lcpEntry.element as HTMLImageElement;
const hasPriority = img.loading !== "lazy";
console.info(
`[LCP Image] src=${img.src}\n` +
` loading=${img.loading}\n` +
` priority=${hasPriority ? "✅ yes" : "❌ missing – add priority prop!"}\n` +
` renderTime=${lcpEntry.renderTime.toFixed(0)}ms`
);
}
}
});
});
observer.observe({ type: "largest-contentful-paint", buffered: true });
return () => observer.disconnect();
}, []);
return null;
}
Conclusion
The next/image component is one of the most impactful performance tools in the Next.js ecosystem. When used correctly, it handles image resizing, format negotiation, lazy loading, and layout stability automatically — translating directly into better Core Web Vitals scores and improved user experience.
Here are the key takeaways from this guide:
- Always use
priorityon your above-the-fold, LCP-candidate image. One missingpriorityprop is often the difference between a green and a red LCP score. - Always supply
sizesthat accurately reflect the rendered width of each image. Vague values like100vwon a 300px thumbnail download three to four times more bytes than necessary. - Enable AVIF in
next.config.ts— it delivers the best compression with the widest modern browser support. - Use
placeholder="blur"for images that are large or slow to load. For remote images, generate theblurDataURLserver-side with a tool likeplaiceholder. - Configure
remotePatternswith the minimum necessary scope. Avoid wildcard hostnames unless you genuinely need them. - Audit your images using Chrome DevTools Lighthouse, the Web Vitals browser extension, or the runtime observer pattern shown in Section 9.
Mastering next/image is not just about following best practices — it is about understanding why each option exists and which trade-offs you are making. With the knowledge in this guide, you have everything you need to ship image-heavy Next.js applications that score well on all Core Web Vitals metrics and provide a fast, stable experience for every user.
Written by Dev Pattern LabsCore Engineering
Dev Pattern Labs is an engineering lab and digital consultancy specializing in custom Next.js web applications, WooCommerce REST API integrations, and workflow automation for growing businesses.
Recommended Companion Articles

Building REST APIs with Next.js Route Handlers
Learn to build production-ready REST APIs with Next.js Route Handlers. Covers GET, POST, PUT, DELETE methods, NextRequest/NextResponse, query params, CORS, streaming responses, and dynamic segments.

Next.js Middleware: Auth Guards, Redirects & Edge Logic
Master Next.js Middleware for authentication guards, redirects, rewrites, and edge logic. Learn matcher config, cookie-based auth, geolocation redirects, and A/B testing patterns.

Server Components vs Client Components in Next.js: When to Use Each
Learn the fundamental differences between React Server Components and Client Components in Next.js. Understand bundle size impact, data access patterns, composition strategies, and the 'use client' boundary.
