Next.js Image Optimization: A Complete Guide to next/image
Images are the most common cause of poor Core Web Vitals. next/image solves most image performance problems automatically — if you use it correctly.
Nextcraft Engineering Team
Why next/image Exists
Raw <img> tags in web applications cause predictable problems:
- No automatic format conversion (no WebP/AVIF for older browsers serving JPEG)
- No responsive sizing (serving a 2000px image to a 400px mobile screen)
- No lazy loading by default (loading off-screen images on page load)
- No dimension reservation (layout shift when images load)
- No blur-up placeholder (jarring appearance when images load)
next/image solves all of these automatically. The tradeoff: you provide dimensions or a fill prop, and the component handles everything else.
Basic Usage
import Image from 'next/image';
// Fixed dimensions — you know the exact display size
<Image
src="/hero.jpg"
alt="Hero image describing what's shown"
width={1200}
height={630}
/>
// Fill mode — image fills a positioned container
<div className="relative h-64 w-full">
<Image
src="/cover.jpg"
alt="Cover image"
fill
className="object-cover"
/>
</div>
The priority Prop: Critical for LCP
The largest above-the-fold image is your LCP element. It should never be lazy-loaded — it should preload:
// Hero image — above the fold, mark as priority
<Image
src="/hero.jpg"
alt="Agency hero"
width={1200}
height={600}
priority // Adds preload link, disables lazy loading
/>
Use priority on:
- The hero image on any page
- The first image in a list that's always visible on load
- Any image that's likely to be the LCP element
Don't use priority on everything — it defeats the purpose by preloading images that are below the fold.
Remote Images: The remotePatterns Config
For images from external domains (a CMS, a CDN, user avatars), configure allowed domains:
// next.config.ts
const nextConfig = {
images: {
remotePatterns: [
{
protocol: 'https',
hostname: 'images.contentful.com',
},
{
protocol: 'https',
hostname: '**.cloudinary.com', // Wildcard subdomain
},
{
protocol: 'https',
hostname: 'avatars.githubusercontent.com',
pathname: '/u/**', // Restrict to specific path
},
],
},
};
Responsive Images with sizes
The sizes prop tells the browser which size image to download based on viewport. Without it, the browser guesses — often poorly:
// Image that's full width on mobile, 50% on tablet, 33% on desktop
<Image
src="/product.jpg"
alt="Product photo"
fill
sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw"
/>
The browser downloads only what it needs. On a 400px mobile screen requesting a 33vw image on a 1440px design, without sizes it downloads the 480px image; with correct sizes it downloads the 400px image. Significant bandwidth savings on mobile.
Blur Placeholders
For images that take time to load (large hero images, high-res photos), a blur placeholder prevents the jarring white flash:
// Static blur placeholder (generated at build time)
import heroImage from './hero.jpg'; // Import as module
<Image
src={heroImage}
alt="Hero"
placeholder="blur" // Uses auto-generated blur data URL
priority
/>
For dynamic images (from a CMS), generate the blur data URL at build/request time:
import { getPlaiceholder } from 'plaiceholder';
async function getImageWithBlur(src: string) {
const buffer = await fetch(src).then(r => r.arrayBuffer());
const { base64 } = await getPlaiceholder(Buffer.from(buffer));
return base64;
}
// In a Server Component
const blurDataURL = await getImageWithBlur(post.coverImage);
<Image
src={post.coverImage}
alt={post.title}
width={1200}
height={630}
placeholder="blur"
blurDataURL={blurDataURL}
/>
Format and Quality
Next.js automatically serves WebP to browsers that support it, and AVIF where supported. You can configure quality:
<Image
src="/photo.jpg"
alt="Photo"
width={800}
height={600}
quality={85} // Default is 75. Range: 1-100
/>
85 is a good balance between quality and file size for photography. UI screenshots can go lower (70–75). Icons and graphics should use SVG instead.
Custom Loaders for CDN Integration
If you're using Cloudinary, Imgix, or another image CDN with its own transformation API:
// lib/imageLoader.ts
import type { ImageLoader } from 'next/image';
const cloudinaryLoader: ImageLoader = ({ src, width, quality }) => {
return `https://res.cloudinary.com/yourcloud/image/upload/w_${width},q_${quality ?? 75},f_auto/${src}`;
};
// Usage
<Image
src="my-image.jpg" // Just the public ID
alt="Image"
width={800}
height={600}
loader={cloudinaryLoader}
/>
Custom loaders let you leverage your CDN's transformation pipeline while keeping the next/image component's other benefits (lazy loading, responsive srcset, placeholder).
Common Mistakes
Nesting <Image> inside <a> without wrapping properly:
// Wrong
<a href="/page"><Image src="..." alt="..." width={100} height={100} /></a>
// Correct — Link wraps Image directly
<Link href="/page"><Image src="..." alt="..." width={100} height={100} /></Link>
Using fill without a positioned parent:
// Wrong — fill needs a positioned parent
<Image src="..." alt="..." fill />
// Correct
<div className="relative h-48">
<Image src="..." alt="..." fill className="object-cover" />
</div>
Empty or generic alt text:
// Wrong
<Image src="..." alt="" />
<Image src="..." alt="image" />
// Correct — describe what the image shows
<Image src="..." alt="Screenshot of the dashboard showing monthly revenue trends" />
Descriptive alt text is both an accessibility requirement and an SEO signal for image search.
Continue reading
Related articles
Why Next.js App Router Is Better for SEO Than Pages Router
The App Router isn't just a new file-system convention — it fundamentally changes how search engines crawl and index your Next.js application.
EngineeringServer Components vs Client Components: Making the Right Call
The boundary between Server and Client Components is the most consequential architectural decision you make in a Next.js application. Here's how to draw it correctly.
EngineeringBuilding High-Performance Next.js Applications for Scale
A deep dive into how we utilize App Router and React Server Components to scale our client builds effectively.
Stay Informed.
Join 1,200+ founders and engineers receiving our monthly deep dives on product engineering, design, and growth.