/ CSS Checker Fixes / Render-Blocking CSS

How to Fix Render-Blocking CSS

CSS blocks page render until fully downloaded and parsed. That's correct behaviour — without it, users would see unstyled HTML flash before CSS arrives. The optimisation is shipping LESS critical CSS so render unblocks sooner. Inline the critical above-the-fold styles, defer everything else. Done properly this lifts First Contentful Paint by 500ms-1.5s on mobile, which Google's Core Web Vitals reward.

1. Identify render-blocking CSS

Step 1
Lighthouse audit
Chrome DevTools → Lighthouse → Generate report. Look for the "Eliminate render-blocking resources" opportunity. It lists every CSS file blocking first paint with the estimated savings.
Step 2
DevTools Network timing
Network tab → reload → look at the "Waterfall" column. CSS files with bars stretching to or past First Contentful Paint are blocking it. Sort by start time to see the request order.

2. Extract critical CSS

Critical CSS is the minimum styles needed for above-the-fold content. Several tools extract it automatically by rendering the page in a headless browser and capturing which CSS rules apply to visible elements.

Critical (npm)

npm install --save-dev critical

// Build script
const critical = require('critical');
await critical.generate({
  src: 'index.html',
  target: { html: 'index-critical.html' },
  width: 1300,
  height: 900,
  inline: true,
});

Next.js

Next.js automatically inlines critical CSS for CSS Modules and styled-jsx. For other CSS, use @next/critical-css or build-step extraction.

Per-route critical CSS

Each route's critical CSS differs (homepage hero is different from product page). Extract per route, not site-wide. Tools like Critical accept a list of URLs and generate per-page critical CSS.

3. Inline critical CSS in head

<head>
  <meta charset="UTF-8">
  <title>Page title</title>
  
  <style>
    /* Critical CSS — 10-20 KB max */
    *{box-sizing:border-box;margin:0;padding:0}
    body{font-family:system-ui,sans-serif;line-height:1.5}
    .header{background:#1e3a8a;color:#fff;padding:16px 24px}
    .hero{padding:64px 24px;text-align:center}
    .hero h1{font-size:48px;margin-bottom:16px}
    /* ... continued for above-the-fold only ... */
  </style>
  
  <!-- Non-critical CSS loaded async -->
  <link rel="preload" href="/main.css" as="style" onload="this.onload=null;this.rel='stylesheet'">
  <noscript><link rel="stylesheet" href="/main.css"></noscript>
</head>

The inlined styles render immediately. The preload kicks off the full CSS download in parallel. When it arrives, the onload handler converts the preload to a stylesheet and the rest of the page styles apply.

⚠️ Don't inline more than 30 KB. Beyond that, the HTML response itself gets too large and First Paint hurts more than CSS does. Stick to genuinely above-the-fold styles.

4. Defer non-critical CSS

Method 1: preload + onload swap (recommended)

<link rel="preload" href="/main.css" as="style" 
      onload="this.onload=null;this.rel='stylesheet'">
<noscript><link rel="stylesheet" href="/main.css"></noscript>

Method 2: media print trick

<link rel="stylesheet" href="/main.css" media="print" 
      onload="this.media='all'">

Browser thinks the CSS is for print, so doesn't block render. JavaScript switches media to "all" once loaded.

Method 3: Async loading via JS

<script>
  const link = document.createElement('link');
  link.rel = 'stylesheet';
  link.href = '/main.css';
  document.head.appendChild(link);
</script>

Works but the JS itself must load first. Less ideal than preload.

5. Optimise font loading

Web fonts also block render if loaded synchronously. The @font-face declaration alone doesn't fetch the font — it's only fetched when CSS references it via font-family.

Preload fonts used above the fold

<link rel="preload" 
      href="/fonts/inter-bold.woff2" 
      as="font" 
      type="font/woff2" 
      crossorigin>

Use font-display: swap

@font-face {
  font-family: 'Inter';
  src: url('/fonts/inter.woff2') format('woff2');
  font-display: swap;  /* show fallback while font loads */
}

swap shows the fallback font immediately, then switches when the web font arrives. Better than block (invisible text until font loads) or auto (browser-dependent).

6. Self-host fonts when possible

Third-party font services (Google Fonts, Adobe Fonts) add DNS + connection setup time on top of font download. For high-traffic sites, self-hosting cuts 100-300ms.

/* Self-hosted instead of Google Fonts */
@font-face {
  font-family: 'Inter';
  src: url('/fonts/inter.var.woff2') format('woff2-variations');
  font-weight: 100 900;
  font-display: swap;
}

Variable fonts (.woff2 with variations) ship one file with all weights, smaller total download.

7. Measure the improvement

Step 1
Lighthouse before/after
Run Lighthouse on the same URL before and after. Expected changes:
  • First Contentful Paint: typically -500ms to -1500ms on mobile
  • Largest Contentful Paint: typically -300ms to -1000ms
  • Lighthouse Performance score: +5 to +15 points
  • "Eliminate render-blocking resources" opportunity: greatly reduced or gone
Step 2
Real user monitoring
Search Console Core Web Vitals report shows real-user 75th-percentile FCP and LCP. Changes typically reflect over 28 days as real traffic accumulates new measurements.
💡 Most performance audits identify CSS as render-blocking long after the actual bottleneck has moved elsewhere. Once critical CSS is inlined and the rest is deferred, focus shifts to JS, images, and server response time. Address render-blocking CSS first because it's the cheapest win.

🎨 Re-run the CSS Checker

Verify render-blocking CSS findings are cleared.

Run CSS Checker →
Related Guides: CSS Checker Fixes  ·  Fix Unused CSS  ·  Fix CSS Bundle Size  ·  Core Web Vitals Guide
💬 Got a problem?