Fix CLS in Nuxt
Cumulative Layout Shift (CLS) is a critical Core Web Vital that measures how quickly the main content of your page becomes visible. In Nuxt 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 Nuxt, with real code examples and before/after performance comparisons. Each step addresses a specific bottleneck in the Nuxt rendering pipeline.
Expected results
Following all five steps typically produces these improvements:
Before
0.25
CLS score (Needs Improvement) -- images without dimensions, font swap shifts, hydration mismatches, dynamic content shifts
After
0.03
CLS score (Good) -- NuxtImage with dimensions, optimized fonts, stable hydration, contained dynamic content
Step-by-step fix
Use NuxtImage with explicit dimensions
The <NuxtImg> and <NuxtPicture> components from @nuxt/image automatically generate width and height attributes when you provide dimensions. This ensures the browser reserves space for images before they load, eliminating the most common CLS source.
width and height props on NuxtImg/NuxtPicture components, especially for remote images. Without dimensions, the browser cannot calculate aspect ratio to reserve space before the image loads.
Prevent font-induced layout shifts with @nuxt/fonts
The @nuxt/fonts module automatically handles font optimization, but you should also configure size-adjusted fallbacks for custom fonts. The module downloads fonts at build time, applies font-display: swap, and generates optimal CSS. For additional CLS protection, define metric-adjusted fallback fonts.
/* @nuxt/fonts handles the primary font automatically */
/* Add metric-adjusted fallbacks for extra CLS protection */
@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 {
/* @nuxt/fonts injects 'Inter' automatically */
/* Add fallback chain for CLS protection */
font-family: 'Inter', 'Inter Fallback', system-ui, sans-serif;
}
Fix hydration mismatches that cause layout shifts
When the server-rendered HTML differs from the client-rendered DOM, Vue 3 performs a hydration mismatch correction that can cause visible layout shifts. Common causes: using Date.now() or Math.random() in templates, browser-specific rendering, and conditional content based on window/document. Use <ClientOnly> for browser-specific content.
{{ product.name }}
{{ product.description }}
Loading map...
{{ product.formattedDate }}
Reserve space for dynamic and async content
Components that load data asynchronously (via useFetch or useAsyncData) can cause CLS when the data arrives and the content expands. Use skeleton placeholders with matching dimensions, CSS min-height, and aspect-ratio to reserve space while data loads.
Reviews
Stabilize v-if/v-show transitions and page transitions
Vue's v-if adds/removes elements from the DOM, potentially shifting surrounding content. For content that toggles visibility, prefer v-show (which uses CSS display:none) to keep the element in the layout flow. For page transitions, use transform-based animations that do not affect layout.
Quick checklist
-
All images use
<NuxtImg>or<NuxtPicture>with dimensions -
@nuxt/fontsinstalled with size-adjusted CSS fallbacks -
Browser-only content wrapped in
<ClientOnly>with sized fallback -
Async content has skeleton placeholders with
min-height -
Page transitions use
opacityonly (no height/position changes) -
v-showpreferred overv-iffor toggle visibility -
No
Date.now(),Math.random(), orwindowin SSR templates
Frequently asked questions
A well-optimized Nuxt 3 site should achieve CLS under 0.05. With NuxtImage dimensions, @nuxt/fonts optimization, proper ClientOnly wrappers, and skeleton loading states, CLS under 0.03 is achievable. The most common CLS sources in Nuxt are image shifts, font loading, and hydration mismatches.
Yes. When the server-rendered HTML differs from client-rendered DOM, Vue 3 corrects the mismatch by updating the DOM, which can cause visible layout shifts. Common causes include using browser APIs (window, document) in setup(), Date/Math.random in templates, and conditional rendering based on client state. Use ClientOnly with dimension-matching fallback placeholders.
The @nuxt/fonts module downloads fonts at build time and self-hosts them, eliminating external requests. It automatically applies font-display: swap and generates preload links. This reduces the time between fallback font display and custom font rendering, minimizing the text reflow that causes CLS. For additional protection, add size-adjusted CSS fallbacks.
Use v-show when toggling visibility of content that should maintain its space in the layout. v-show uses CSS display:none, keeping the element in the DOM but hidden. Use v-if with a fixed-height container when you need to conditionally render content that should not occupy space when absent. The key is ensuring the container dimensions stay stable regardless of content state.
Use the Web Vitals Chrome extension for real-time CLS scores, then open Chrome DevTools Performance panel to record a page load. Look for Layout Shift entries in the Experience track. For hydration mismatches, check the Vue DevTools Hydration Mismatch tab. Enable Nuxt's debug mode in development to see hydration warnings in the console.
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 Nuxt, 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 Nuxt
Related performance optimization for the same framework.
GuideComplete CLS Guide
Deep dive into CLS -- thresholds, measurement, and optimization strategies.
FixFix CLS in Vue 3
Compare CLS fixes in the Vue ecosystem.
ToolCWV Score Explainer
Enter your scores for personalized fix recommendations.