Skip to main content
CodeRocket
CSShighloading

Load CSS without blocking render

rule · css-non-blocking

CSS files should be loaded in a non-blocking manner to prevent them from delaying DOM parsing and initial page rendering.

Code Example

HTML
<!-- ❌ Blocking CSS - delays DOM parsing -->
<head>
    <link rel="stylesheet" href="styles.css">
    <link rel="stylesheet" href="large-library.css">
</head>

Why It Matters

Render-blocking CSS delays First Contentful Paint—users see a blank screen while waiting for stylesheets to download and parse.

Non-Blocking Solutions

1. Preload with Media Attribute

HTML
<head>
    <!-- Critical CSS inline -->
    <style>
        /* Critical above-the-fold styles */
        .header { background: #000; color: #fff; }
        .hero { min-height: 50vh; }
    </style>
 
    <!-- Non-blocking CSS loading -->
    <link rel="preload" href="styles.css" as="style" onload="this.onload=null;this.rel='stylesheet'">
    <noscript><link rel="stylesheet" href="styles.css"></noscript>
 
    <!-- Load non-critical CSS with media attribute -->
    <link rel="stylesheet" href="print.css" media="print">
    <link rel="stylesheet" href="mobile.css" media="screen and (max-width: 768px)">
</head>

2. LoadCSS Library Implementation

HTML
<head>
    <!-- Critical CSS inline -->
    <style>
        /* Critical styles here */
    </style>
 
    <!-- LoadCSS script -->
    <script>
        /*! loadCSS. [c]2017 Filament Group, Inc. MIT License */
        !function(a){"use strict";var b=function(b,c,d){function e(a){return h.body?a():void setTimeout(function(){e(a)})}function f(){i.addEventListener&&i.removeEventListener("load",f),i.media=d||"all"}var g,h=a.document,i=h.createElement("link");if(c)g=c;else{var j=(h.body||h.getElementsByTagName("head")[0]).childNodes;g=j[j.length-1]}var k=h.styleSheets;i.rel="stylesheet",i.href=b,i.media="only x",e(function(){g.parentNode.insertBefore(i,c?g:g.nextSibling)});var l=function(a){for(var b=i.href,c=k.length;c--;)if(k[c].href===b)return a();setTimeout(function(){l(a)})};return i.addEventListener&&i.addEventListener("load",f),i.onloadcssdefined=l,l(f),i};"undefined"!=typeof exports?exports.loadCSS=b:a.loadCSS=b}("undefined"!=typeof global?global:this);
    </script>
 
    <!-- Load non-critical CSS -->
    <script>
        loadCSS('styles.css');
        loadCSS('components.css');
        loadCSS('vendor.css');
    </script>
 
    <!-- Fallback for no-JS -->
    <noscript>
        <link rel="stylesheet" href="styles.css">
        <link rel="stylesheet" href="components.css">
        <link rel="stylesheet" href="vendor.css">
    </noscript>
</head>

Framework Examples

Next.js (Automatic Optimization)

JSX
// Next.js automatically optimizes CSS loading
import './globals.css'  // Critical CSS
import './components.css'  // Non-critical CSS
 
export default function RootLayout({ children }) {
  return (
    <html lang="en">
      <body>{children}</body>
    </html>
  )
}

React with Critical CSS

JSX
// Critical CSS component
import { useEffect } from 'react'
 
function App() {
  useEffect(() => {
    // Load non-critical CSS after component mounts
    const loadCSS = (href) => {
      const link = document.createElement('link')
      link.rel = 'stylesheet'
      link.href = href
      document.head.appendChild(link)
    }
 
    loadCSS('/styles/non-critical.css')
    loadCSS('/styles/components.css')
  }, [])
 
  return (
    <div className="app">
      <style jsx>{`
        /* Critical inline styles */
        .app {
          min-height: 100vh;
          display: flex;
          flex-direction: column;
        }
      `}</style>
      {/* Your app content */}
    </div>
  )
}

Vue.js with Async CSS

VUE
<template>
  <div class="app">
    <!-- Your app content -->
  </div>
</template>
 
<script>
export default {
  mounted() {
    // Load non-critical CSS after mount
    this.loadCSS('/styles/components.css')
    this.loadCSS('/styles/animations.css')
  },
  methods: {
    loadCSS(href) {
      const link = document.createElement('link')
      link.rel = 'preload'
      link.as = 'style'
      link.href = href
      link.onload = function() {
        this.onload = null
        this.rel = 'stylesheet'
      }
      document.head.appendChild(link)
    }
  }
}
</script>
 
<style scoped>
/* Critical component styles */
.app {
  min-height: 100vh;
}
</style>

Modern CSS Loading Strategies

1. Critical CSS Inlining

HTML
<head>
    <style>
        /* Inline critical CSS - above-the-fold content */
        body { margin: 0; font-family: Arial, sans-serif; }
        .header { background: #000; color: #fff; height: 60px; }
        .hero { min-height: 50vh; background: #f0f0f0; }
    </style>
</head>

2. Resource Hints

HTML
<head>
    <!-- Preconnect to font/CDN domains -->
    <link rel="preconnect" href="https://fonts.googleapis.com">
    <link rel="preconnect" href="https://cdn.jsdelivr.net">
 
    <!-- Preload critical CSS -->
    <link rel="preload" href="critical.css" as="style">
 
    <!-- Prefetch non-critical CSS -->
    <link rel="prefetch" href="animations.css">
</head>

3. Service Worker CSS Caching

JavaScript
// sw.js
self.addEventListener('fetch', (event) => {
  if (event.request.destination === 'style') {
    event.respondWith(
      caches.match(event.request)
        .then(response => response || fetch(event.request))
    )
  }
})

Performance Benefits

  • Faster First Paint: DOM parsing isn't blocked by CSS downloads
  • Better Core Web Vitals: Improved LCP (Largest Contentful Paint)
  • Progressive Enhancement: Page is functional while styles load
  • Perceived Performance: Users see content sooner

Build Tool Integration

Webpack

JavaScript
// webpack.config.js
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
 
module.exports = {
  plugins: [
    new MiniCssExtractPlugin({
      filename: '[name].[contenthash].css',
      chunkFilename: '[id].[contenthash].css',
    })
  ],
  optimization: {
    splitChunks: {
      cacheGroups: {
        styles: {
          name: 'styles',
          test: /\.css$/,
          chunks: 'all',
          enforce: true,
        },
      },
    },
  },
}

Vite

JavaScript
// vite.config.js
import { defineConfig } from 'vite'
 
export default defineConfig({
  build: {
    cssCodeSplit: true,
    rollupOptions: {
      output: {
        manualChunks: {
          critical: ['./src/styles/critical.css'],
          components: ['./src/styles/components.css']
        }
      }
    }
  }
})

Tools & Libraries

Support Notes

  • Non-blocking CSS strategies can behave differently depending on preload, stylesheet priority, and browser loading heuristics, so verify the final page output in target browsers.
  • Document the fallback when a loading optimization depends on browser support or a framework-specific resource pipeline.

Verification

Automated Checks

  • Lighthouse: Check for render-blocking resources
  • Chrome DevTools: Network tab to see resource loading order

Manual Checks

  • PageSpeed Insights: Analyze CSS loading performance
  • WebPageTest: Visualize CSS loading timeline