
Stop Using `<img>` in Next.js: Why `next/image` is Essential for LCP
Your beautiful hero image killed the LCP score? Understand how `next/image` optimizes format (WebP/AVIF), resizing, and layouts. Deep dive into `sizes` prop and `blurDataURL`.

Your beautiful hero image killed the LCP score? Understand how `next/image` optimizes format (WebP/AVIF), resizing, and layouts. Deep dive into `sizes` prop and `blurDataURL`.
Optimizing by gut feeling made my app slower. Learn to use Performance profiler to find real bottlenecks and fix what matters.

Text to Binary (HTTP/2), TCP to UDP (HTTP/3). From single-file queueing to parallel processing. Google's QUIC protocol story.

From HTML parsing to DOM, CSSOM, Render Tree, Layout, Paint, and Composite. Mastering the Critical Rendering Path (CRP), Preload Scanner, Reflow vs Repaint, and requestAnimationFrame.

Obsessively wrapping everything in `useMemo`? It might be hurting your performance. Learn the hidden costs of memoization and when to actually use it.

<img> in Next.js: Why next/image is Essential for LCPI added a high-res hero image to the main page.
I dumped the designer's hero.png (3MB) straight into an <img> tag.
Run Lighthouse... Performance: 45. A glaring red warning. LCP (Largest Contentful Paint) took 4.2 seconds. Users were staring at a white screen for 4 seconds.
"It's just one image! Why did it tank my score?"
I thought "Images are supposed to be heavy." I thought I could serve the same file to mobile and desktop.
But browsers aren't smart.
Give a mobile processing a 3MB file, and it downloads all 3MB. The phone heats up decoding it.
Plus, png is terribly inefficient for photos.
I understood it as a "Universal Adapter Pipeline."
<img>: Throwing the raw file. "Here, take this 3MB rock."next/image: An intelligent server that tailor-makes clothes.
I didn't need to manually create hero-mobile.webp.
Next.js server does it On-Demand.
next/imageImporting local files lets Next.js calculate dimensions automatically.
import Image from 'next/image';
import heroImg from '../public/hero.png';
// Before ❌
// <img src="/hero.png" alt="Hero" />
// After ✅
<Image
src={heroImg}
alt="Hero"
placeholder="blur" // Blur-up effect! (Auto generated)
priority // Critical for LCP!
/>
priority tells the browser to Preload this image, skipping lazy loading. This is the key to Green LCP.
For AWS S3 URLs, you must specify dimensions and allow the domain.
// next.config.js
module.exports = {
images: {
domains: ['my-bucket.s3.amazonaws.com'],
},
}
<Image
src="https://my-bucket.s3.amazonaws.com/user.jpg"
alt="User"
width={300} // Required for aspect ratio calculation
height={300}
/>
sizes (Crucial!)Many people ignore sizes prop. Without it, mobile users might download desktop-sized images.
Especially when using fill.
<div style={{ position: 'relative', height: '400px' }}>
<Image
src="/banner.jpg"
fill
style={{ objectFit: 'cover' }}
// "If viewport < 768px, image is 100vw wide. Else, it's 50vw."
sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw"
/>
</div>
This tells Next.js which srcset to generate.
If omitted, defaults to 100vw, meaning 1920px image on a 320px phone. Bad.
<img> causes content to jump when loading finishes.
next/image reserves space using dimensions or placeholders. Zero layout shift.
Next.js cannot auto-generate blur placeholders for remote URLs during build.
You must provide blurDataURL manually (Base64 string).
<Image
src={remoteUrl}
placeholder="blur"
blurDataURL="data:image/jpeg;base64,/9j/4AAQSkZJRg..." // Generate with 'plaiceholder'
/>
Enable AVIF (better than WebP) in config:
// next.config.js
images: {
formats: ['image/avif', 'image/webp'],
}
If you use Cloudinary or Imgix, use a loader to construct their specific URLs.
const myLoader = ({ src, width, quality }) => {
return `https://example.com/${src}?w=${width}&q=${quality || 75}`
}
Next.js uses Squoosh (WASM) by default. It's portable but slow. For production, you MUST serve using Sharp (Native).
npm install sharp
Just installing this boosts image optimization speed by 3-4x. Since Serverless Functions (Lambda) are billed by execution time, faster compression = Lower Bills.
Control exactly what variants are generated in next.config.js.
module.exports = {
images: {
// Viewport width breakpoints (responsive/fill)
deviceSizes: [640, 750, 828, 1080, 1200, 1920, 2048, 3840],
// Image width breakpoints (fixed/intrinsic)
imageSizes: [16, 32, 48, 64, 96, 128, 256, 384],
},
}
If your CSS breakpoints are just 600px and 1000px, you don't need 8 different variants.
Tailor deviceSizes to your CSS framework to save storage and build time.
I optimized a startup's landing page. Marketing uploaded 5 uncompressed raw photos (3MB each) into a Carousel. Total 15MB.
<img>.next/image auto-converted to WebP. 15MB -> 1.2MB (-92%).priority. Others stayed lazy.sizes="100vw". Mobile users downloaded 400px versions, not 4000px.LCP dropped from 6.8s to 0.9s. Bounce rate dropped to 20%. CRO (Conversion Rate Optimization) skyrocketed. Performance isn't just vanity; it's Revenue.
Q: What if I don't know the width/height (CMS)?
A: Use fill. But ensure the parent div has position: relative and a defined height (or aspect-ratio CSS).
Q: Images are slow in npm run dev.
A: Next.js optimizes images on-demand and doesn't aggressively cache in dev mode. In npm start (production), aggressive caching kicks in.
Q: Should I use Image for SVGs?
A: No. SVGs are vectors. Just use <img> or inline <svg>. next/image might rasterize them, which ruins the infinite scaling.
picture element)Sometimes, resizing isn't enough.
next/image handles resizing, but not Cropping (Art Direction).
For this, you still need standard HTML/CSS techniques, or use two Image components with CSS display toggles.
<div className="mobile-only">
<Image src="/tree-cropped.jpg" ... />
</div>
<div className="desktop-only">
<Image src="/landscape.jpg" ... />
</div>
This ensures the user sees the Right Content, not just the Right Size.
Images are the heaviest assets on the web. Optimizing them is the single highest-ROI activity for web performance.
next/image is a "Magic Box."
All you have to do is replace <img> with <Image> and add sizes.
It's a small change for a developer, but a giant leap for User Experience.
Summary:
<img> is a 3MB bomb. Use next/image to serve optimized formats and sizes. Don't forget the sizes prop!