Fix CLS in Astro
Cumulative Layout Shift (CLS) is a critical Core Web Vital that measures how quickly the main content of your page becomes visible. In Astro applications, common issues include unoptimized images, render-blocking resources, and inefficient data loading patterns that delay the largest visible element.
This guide walks through five targeted fixes for CLS in Astro, with real code examples and before/after performance comparisons. Each step addresses a specific bottleneck in the Astro rendering pipeline.
Expected results
Following all five steps typically produces these improvements:
Before
0.18
CLS score (Needs Improvement) -- images without dimensions, font swap shifts, island hydration layout changes
After
0.02
CLS score (Good) -- all images sized, font fallback adjusted, stable island placeholders
Step-by-step fix
Always specify image dimensions with astro:assets
The most common CLS source in any framework is images loading without reserved space. Astro's <Image /> component automatically adds width and height attributes, but you must provide the source image dimensions. For remote images, always specify width and height props explicitly.
<img> tags directly instead of the Astro Image component, you must manually add width and height attributes, or use CSS aspect-ratio to reserve space.
---
// Missing dimensions causes layout shift
---

---
import { Image } from 'astro:assets';
import banner from '../assets/banner.jpg'; // Dimensions auto-detected
---
Prevent font swap layout shifts with size-adjusted fallbacks
When custom fonts load with font-display: swap, the fallback font is replaced, often causing text to reflow. Use CSS size-adjust, ascent-override, and descent-override on your fallback font face to match the metrics of your custom font. Tools like Fallback Font Generator calculate these values automatically.
/* Match fallback metrics to custom font */
@font-face {
font-family: 'Inter';
src: url('/fonts/inter-var-latin.woff2') format('woff2');
font-weight: 100 900;
font-display: swap;
}
/* Adjusted fallback prevents CLS during font swap */
@font-face {
font-family: 'Inter Fallback';
src: local('Arial');
ascent-override: 90.49%;
descent-override: 22.56%;
line-gap-override: 0%;
size-adjust: 107.64%;
}
body {
font-family: 'Inter', 'Inter Fallback', sans-serif;
}
Reserve space for hydrating islands
When Astro islands hydrate (components with client:visible or client:idle), they can cause layout shift if the hydrated component renders at a different size than the server-rendered HTML. Ensure your island wrapper has fixed dimensions or uses CSS min-height to reserve space before hydration completes.
---
import SearchWidget from '../components/SearchWidget';
---
Stabilize dynamic content with CSS contain and aspect-ratio
For elements that load asynchronously (ads, embeds, dynamically injected content), use CSS aspect-ratio and contain: layout to prevent shifts. Define explicit containers for any content that loads after the initial render.
/* Video embed container */
.video-embed {
aspect-ratio: 16 / 9;
width: 100%;
background: var(--color-surface-2);
contain: layout;
}
/* Ad slot with reserved space */
.ad-slot {
min-height: 250px;
width: 300px;
contain: layout style;
background: var(--color-surface-2);
}
/* Dynamic content area */
.dynamic-content {
min-height: 200px;
contain: layout;
/* Smooth transitions instead of sudden shifts */
transition: min-height 0.2s ease;
}
Handle view transitions without layout shift
Astro's View Transitions can cause CLS if elements move position between pages. Use transition:name to create smooth morph animations between shared elements, and avoid transition:animate='slide' on content that changes size between pages. For the safest CLS scores, use transition:animate='fade' as the default.
---
import { ViewTransitions } from 'astro:transitions';
---
Quick checklist
-
All images use
<Image />fromastro:assetswith dimensions -
Remote images have explicit
widthandheightprops -
Font fallbacks use
size-adjustand metric overrides -
Island wrappers have
min-heightto reserve space -
Dynamic content uses
aspect-ratioandcontain: layout -
View transitions use
fadeormorph(notslide) -
No unsized
<img>,<video>, or<iframe>elements
Frequently asked questions
Astro sites typically achieve CLS scores under 0.05 because they generate static HTML with no hydration-related layout shifts in the main content. The most common CLS issues in Astro come from images without dimensions, font loading, and island hydration. With the optimizations in this guide, CLS under 0.02 is achievable.
Islands can cause CLS if the hydrated component renders at a different size than the server-rendered HTML placeholder. This is most common with client:visible components that expand when they hydrate. Use min-height on the wrapper element and CSS contain to isolate the island's layout impact. Components with client:idle typically hydrate before the user notices any shift.
View transitions can cause CLS during page navigation if elements change position or size between pages. Use transition:animate='fade' as a safe default, and transition:name on shared elements (headers, navigation) to create smooth morph animations. Avoid 'slide' animations on content areas where the incoming page has different dimensions.
Yes, when using the Image component from astro:assets with local images. Astro reads the image file at build time and automatically sets width and height. For remote images, you must provide dimensions explicitly via the width and height props. Without these, the browser cannot reserve space and layout shift will occur.
Use Chrome DevTools Performance panel to record a page load, then look for Layout Shift entries in the Experience row. The CLS Debugger extension highlights shifted elements in red. For Astro-specific issues, check if shifts correlate with island hydration by temporarily removing client:* directives. Use the Web Vitals Chrome extension for real-time CLS monitoring.
Set up real-user monitoring using the web-vitals JavaScript library (1.5KB). Send CLS data to your analytics platform (Google Analytics 4, custom endpoint). The attribution build identifies exactly which element caused each layout shift. For Astro, also monitor CLS after route transitions, as client-side navigation can trigger additional shifts not captured in initial page load.
Continue learning
Fix LCP in Astro
Related performance optimization for the same framework.
GuideComplete CLS Guide
Deep dive into CLS -- thresholds, measurement, and optimization strategies.
FixFix CLS in Next.js
Compare CLS fixes across different frameworks.
ToolCWV Score Explainer
Enter your scores for personalized fix recommendations.