Reserve image space

Give next/image a width and height, or fill a sized parent.

From next-best-practices, image.md.

Same article, same slow image

Set the delay, press Load article, watch the paragraph.

No dimensionsBad
dusk.jpgidle

Field notes

Dusk over the lake

4 min read

The light goes first on the far ridge, then the water holds it for a few minutes longer.

Continue reading

CLS0.000
<img src="/dusk.jpg" alt="…" />
width + heightGood
dusk.jpgidle

Field notes

Dusk over the lake

4 min read

The light goes first on the far ridge, then the water holds it for a few minutes longer.

Continue reading

CLS0.000
<Image … width={800} height={450} placeholder="blur" />
import Image from 'next/image'
import dusk from './dusk.jpg'

// Local import: size and blur come from the file
<Image src={dusk} alt="Mountains over a lake at dusk" placeholder="blur" />

// Remote: say the size yourself
<Image
  src="https://cdn.example.com/dusk.jpg"
  alt="Mountains over a lake at dusk"
  width={800}
  height={450}
/>

// Unknown size: fill a parent that owns the box
<div style={{ position: 'relative', height: 400 }}>
  <Image src="/dusk.jpg" alt="Mountains over a lake at dusk" fill
    sizes="100vw" style={{ objectFit: 'cover' }} />
</div>

What fills the box meanwhile

Scrub the clock or press Play.

placeholder="empty"Waiting

Dusk over the lake

0 B
ColorWaiting

Dusk over the lake

0 B
placeholder="blur"Waiting

Dusk over the lake

12×7 px inline
// Local import: blur generated at build time
<Image src={dusk} alt="Mountains over a lake at dusk" placeholder="blur" />

// Remote: pass a tiny base64 image yourself
<Image src="https://cdn.example.com/dusk.jpg" alt="Mountains over a lake at dusk"
  width={800} height={450}
  placeholder="blur" blurDataURL="data:image/jpeg;base64,/9j/4AAQSkZJRg..." />

// Or a flat color
<Image src="https://cdn.example.com/dusk.jpg" alt="Mountains over a lake at dusk"
  width={800} height={450}
  placeholder="empty" style={{ backgroundColor: '#e0e0e0' }} />

The aspect-ratio trap

Change viewport and density; 16×9 ships a 16px file.

width={16} height={9}Bad
32 × 18
fill + sizes="100vw"Good
640 × 360
// Bad: read as 16px wide, srcset offers 16w and 32w
<Image src="/dusk.jpg" alt="…" width={16} height={9} className="w-full h-auto" />

// Good: the real display size…
<Image src="/dusk.jpg" alt="…" width={1200} height={675} />

// …or fill a sized parent and say how wide it renders
<div className="relative aspect-video">
  <Image src="/dusk.jpg" alt="…" fill sizes="100vw" style={{ objectFit: 'cover' }} />
</div>

Why it matters, pros and cons

Why it matters

Without a size nothing is reserved, and the text jumps when the image lands.

Pros

  • Image-driven CLS drops to 0.
  • Local imports get size and blur free.
  • Real dimensions also pick a sharp file.

Cons

  • Remote images need a size you may not know.
  • fill in a parent without height collapses to 0.
  • blurDataURL ships inside the HTML; keep it tiny.