menu
Hockey goalie

Stop Render-Blocking CSS In Nuxt 3

When it comes to page performance and web vitals, small tweaks can make a big difference. One example is Largest Contentful Paint (LCP) and it's relationship with Cascading Style Sheets (CSS). When you visit a site, the server should send back a full page of HTML for your browser to start rendering.

But there's a catch: If that HTML contains references to CSS files, the browser can't start rendering the page until that CSS is fully downloaded and parsed!

Global CSS

By default Nuxt 3 will inject your global CSS into a file called entry.css (with a hash), and link to it from the server response.

CSS in HTML

Nuxt tries to speed up this download by adding a preload tag early on, but honestly this doesn't do much since the CSS file is discovered very early by the preload scanner. Even without this preload, the scanner finds the CSS reference before the page is even done downloading!

The Problem

Since the browser has to download entry.css before it can render the page, any little hickup in your internet connection could directly affect your site experience and core web vitals! If you use UI frameworks (cough Tailwind) this file could also become pretty large.

Keep in mind, when it comes to web performance the problem often isn't only network speed, it's the delay! If your website is in the United States and someone is hitting it from Asia, it will take a while to even start downloading your file because the packets have to physically travel across the planet!

Option 1: Inline Your Global CSS

One solution is to avoid forcing the browser to download this file at all. Inject it into the initial server response instead!

This technique may not work if other Nuxt Modules inject their own CSS into the build process.

Move Nuxt Config CSS Files Into App.vue

To get your global CSS into the initial server response you have to move any css files from your nuxt.config.ts into App.vue.

nuxt.config.ts
export default defineNuxtConfig({
  css: ['@/assets/css/main.css'],
})
app.vue
<style lang="postcss">
@import '@/assets/css/main.css';
</style>

If you use a custom error.vue file, be sure to add the imports there too.

Nuke The Entry.css File

Since we are injecting our global CSS into the App, we no longer need this global file.

nuxt.config.ts
export default defineNuxtConfig({
  hooks: {
    'build:manifest': (manifest) => {
      // find the app entry, css list
      const css = manifest['node_modules/nuxt/dist/app/entry.js']?.css
      if (css) {
        // start from the end of the array and go to the beginning
        for (let i = css.length - 1; i >= 0; i--) {
          // if it starts with 'entry', remove it from the list
          if (css[i].startsWith('entry')) css.splice(i, 1)
        }
      }
    },
  },
})

Success!

After you've made these two tweaks you should no longer see your entry.css file and instead see your CSS within your page.

Inline CSS

What About [Page].css files?

When you first hit a page or navigate to a new page you may see more CSS files being downloaded. What's up with those? Are they a problem?

Network tab output

The short answer is no.

When Nuxt 3 is hydrating the page on initial load, it runs the Javascript for the page. Most likely the same code that would be ran if you had hit the page client-side which would include all of the CSS needed to render the new virtual page.

Technically on first page hits those styles are duplicated but at least they aren't render blocking since they are added via Javascript.

Nuxt 2 did the same thing, you just didn't notice because the styles weren't extracted into their own CSS files. They were embeded right into the Javascript files.

Option 2: Keep The CSS File

If you insist on keeping your global CSS in a seperate file, at least do these basic steps to cut down on the performance hit.

  • Use a CDN so the files are physically closer to your users
  • Compress your assets, ideally using Brotli
  • Use HTTP2/HTTP3 for delivery
  • Host your assets on your own domain (snarf.com, NOT assets.snarf.com)

Using all of these techniques, you could easily speed up the download of your global CSS by as much as 300ms on slower mobile connections!