Images are the single biggest cause of Cumulative Layout Shift on the web. Late-loading images push surrounding content down as they arrive, creating the jarring "page jumping" experience users hate and Google penalises. CLS is one of the three Core Web Vitals, and image CLS is fixable with attribute-level changes. This guide covers the reserved-space patterns, aspect-ratio fallbacks for unknown dimensions, placeholder strategies, and verification.
What happens without reserved space:
<img> with no dimensionsWith reserved space:
<img width="800" height="600"><!-- Wrong --> <img src="/hero.jpg" alt="..."> <!-- Right --> <img src="/hero.jpg" alt="..." width="1200" height="675">
Use intrinsic dimensions — the natural pixel dimensions of the source file. Browser computes aspect ratio (16:9 here) and reserves matching space. CSS can scale display size; aspect ratio stays the same.
Pair with CSS for responsive:
img {
max-width: 100%;
height: auto;
}
For images with unknown dimensions at template render time (user uploads, dynamic content), CSS aspect-ratio reserves the right space:
<div class="image-frame">
<img src="/uploads/abc.jpg" alt="...">
</div>
<style>
.image-frame {
aspect-ratio: 16 / 9;
background: #f0f0f0;
overflow: hidden;
}
.image-frame img {
width: 100%;
height: 100%;
object-fit: cover;
}
</style>
Browser reserves the 16:9 frame immediately. Image loads inside it, cropped or scaled by object-fit. Regardless of actual image dimensions, the frame stays at 16:9 — no shift.
Even with reserved space, an empty rectangle then sudden image appearance can feel jarring. Soft transition with a placeholder colour:
<img src="/hero.jpg"
width="1200" height="675"
alt="..."
style="background: #f0f0f0;">
Tiny base64-encoded blur of the full image. Loads instantly with the HTML, full image fades in over it.
<img src="data:image/jpeg;base64,iVBORw0KGgoA..."
data-src="/hero.jpg"
width="1200" height="675"
alt="...">
<!-- Modern: BlurHash or thumbhash -->
<div class="placeholder" style="background-image: url(blur.svg)">
<img src="/hero.jpg" width="1200" height="675">
</div>
import Image from 'next/image';
import heroImage from './hero.jpg';
<Image src={heroImage} alt="..." placeholder="blur" />
// Auto-generates blurred placeholder, served inline as base64
JavaScript-injected images (ads, embedded widgets, lazy-loaded media) often cause CLS because they appear after layout settles. Reserve space ahead of time:
<!-- Ad slot reserves 300x250 before ad loads -->
<div class="ad-slot" style="width: 300px; height: 250px; background: #f5f5f5">
<script>
googletag.cmd.push(function() {
googletag.display('ad-slot-1');
});
</script>
</div>
Same pattern for embeds:
<!-- YouTube embed with reserved space --> <div style="aspect-ratio: 16/9; background: #000"> <iframe src="..." loading="lazy" width="100%" height="100%"></iframe> </div>
<!-- Bad: CSS won't prevent CLS --> <img src="/hero.jpg" style="width: 1200px; height: 675px">
CSS applies after stylesheet loads. HTML attributes work immediately. Use both for safety — attributes for layout, CSS for responsive sizing.
// WordPress example — some themes strip dimensions
function add_image_dimensions($content) {
// Bad theme code might do this
return preg_replace('/width="\d+"\s+height="\d+"/', '', $content);
}
// Don't strip them. Browsers need them.
<!-- Bad: mobile variant has different ratio -->
<img srcset="/mobile-400x600.jpg 400w,
/desktop-1200x675.jpg 1200w"
width="1200" height="675">
<!-- Browser reserves 16:9 but mobile image is 2:3 -->
<!-- Image renders cropped or distorted -->
For art-directed crops, use picture with separate sources and dimensions per breakpoint.
One page-wide template change typically fixes most image CLS:
<img> in template gets width and height attributesimg { max-width: 100%; height: auto; }aspect-ratiowidth and height attributes to the image-rendering helper or component in your codebase. One template change patches every image site-wide. Most sites with CLS problems are missing this one thing.Verify CLS findings and measure improvement.
Run Image Audit →