3 min read

Fixing Cumulative Layout Shift (CLS) in Nuxt 3

Poor performance score in PageSpeed Insights

Cumulative Layout Shift (CLS) is a measurement of how much a webpage’s content unexpectedly moves around while it loads. It is one of the Core Web Vitals measured by Google in both its Lighthouse tool and the Chrome User Experience Report (CrUX). Now that Google uses Core Web Vitals as a ranking factor, CLS is more important than ever.

Unfortunately, I recently experienced an issue where some of the CSS in my Nuxt 3 application (Random Episodes) was loading late, causing a jarring visible jump when the styles loaded and exceedingly high CLS scores. In fact, every single URL was failing the Core Web Vitals assessment in Google Search Console. The CLS scores in PageSpeed Insights ranged from 0.417 to 0.86 depending on page and device (for reference, anything above 0.25 is considered poor).

Identifying the issue

When I inspected the styles that were responsible, I saw that they were being placed near the end of the <head> tag, after almost all of the other scripts and stylesheets. This caused them to load too late, triggering the layout shifts. After some research, I learned that Nuxt ships both inline styles and separate CSS files by default. The CSS files load later because they should be redundant, but for some reason in my case the inline styles were failing to include all of the CSS. I also found that due to differences in bundling behavior for the development server, the issue only affects production builds.

The solution

After some experimentation, I found that one way to fix the problem is to disable the inline styles feature in nuxt.config.ts. This results in the relevant CSS file being placed higher in the document, loading in time to avoid the layout shift.

Here’s an abbreviated example of the updated Nuxt configuration:

// nuxt.config.ts

export default defineNuxtConfig({
  features: {
    inlineStyles: false
  }
})

Making this change brought my local CLS scores from an average of around 0.77 down to a perfect 0.00! It hasn’t been long enough since deploying the change to see whether CrUX and Core Web Vitals show an improvement, but my fingers are crossed.

Note:

Nuxt’s inlineStyles feature is currently only available when using Vite as the bundler, so anyone using Webpack will have to look elsewhere for potential causes and solutions to any CLS issues.