An Avatar component using Next.js `<Image>`. Author left out width and height. Image loads, the page reflows, every other element shifts down by the avatar's height. CLS score craters.
The vulnerable diff
// components/Avatar.tsx
import Image from "next/image";
export function Avatar({ src }: { src: string }) {
// BUG: no width/height — image loads asynchronously, shifts layout
return <Image src={src} alt="avatar" />;
}What is wrong
Next.js's `<Image>` component (since 13.x) requires explicit dimensions to reserve space in the layout before the image loads. Without dimensions, the browser does not know how tall the slot will be — renders 0 px tall initially, then resizes to actual image height when the bytes arrive, pushing everything below down. This is Cumulative Layout Shift (CLS) — a Core Web Vitals metric that Google directly factors into search ranking.
The attack
Lighthouse audit shows the impact:
Cumulative Layout Shift: 0.34 (Poor; target < 0.1)
- Largest contributor: <Avatar> reflow on profile pages
- Effective ranking penalty for the URL
Web Vitals are part of the Page Experience signal in Google search.
A poor CLS score on high-traffic pages directly costs traffic.User-visible symptom: page renders, user starts to click the button, button moves away as image loads. Mis-clicks, frustration.
Mesrai's review comment
mesraipilot · Bot · reviewed 30 sec ago
[mesrai] [code-review] [Performance] [CLS] [medium]
`<Image>` without width/height/fill. Browser cannot reserve space for
the image — when it loads, the layout shifts. CLS Web Vitals score
suffers.
Three correct shapes:
// Known size:
<Image src={src} alt="avatar" width={48} height={48} />
// Responsive within a sized container (use fill):
<div className="relative w-12 h-12">
<Image src={src} alt="avatar" fill />
</div>
// Lazy + above-the-fold tuning:
<Image src={src} alt="avatar" width={48} height={48}
priority loading="eager" />
Always provide dimensions one way or another. The Image component
will not let you skip it intentionally — and that constraint exists
because of CLS.The fix
// components/Avatar.tsx — fixed
import Image from "next/image";
export function Avatar({ src, size = 48 }: { src: string; size?: number }) {
return <Image src={src} alt="avatar" width={size} height={size} />;
}Explicit dimensions reserve space in the layout. Image bytes load into the reserved slot — no reflow. Layout shift score stays low. For responsive use cases where the parent decides the size, use `fill` with a positioned parent.
Why human review missed it
Image-shift CLS bugs are easy to miss because dev environments often have fast network — the image loads before the user can see the shift. Mesrai catches every Next.js `<Image>` without width/height/fill props.
Related rules + further reading
Mesrai rule pack: performance/next-image-dimensions — flags `<Image>` without width/height/fill.
web.dev: Cumulative Layout Shift.
Next.js docs: <Image> Component.
Takeaway
Width and height. Or `fill` in a positioned parent. CLS score depends on it. Mesrai catches every undimensioned Image.