Skip to content

Critical CSS and Extraction

advanced10 min read

CSS Blocks Rendering — Critical CSS Unblocks It

CSS is render-blocking. The browser won't paint a single pixel until it has downloaded and parsed ALL linked stylesheets. A 200KB CSS file that contains styles for your entire application delays the first paint for every page — even if the current page only needs 5KB of those styles.

Critical CSS solves this by inlining the CSS needed for the above-the-fold content directly in the HTML. The rest loads asynchronously. The result: the browser paints immediately instead of waiting for the full stylesheet.

Mental Model

Think of critical CSS as a restaurant that serves bread immediately. The full meal (complete stylesheet) takes time to prepare. But the bread basket (critical CSS inlined in <head>) arrives the moment you sit down. You're not staring at an empty table (blank screen) while the kitchen works. The main courses (deferred stylesheets) arrive shortly after, completing the experience.

How Critical CSS Works

Step 1: Identify Above-the-Fold CSS

The "critical path" is the CSS needed to render what the user sees before scrolling:

<!DOCTYPE html>
<html>
<head>
  <!-- CRITICAL: Inlined styles for above-the-fold content -->
  <style>
    /* Only what's needed for the initial viewport */
    :root { --bg: #fff; --text: #1a1a2e; }
    body { margin: 0; font-family: system-ui; background: var(--bg); color: var(--text); }
    .header { display: flex; align-items: center; padding: 1rem 2rem; }
    .hero { padding: 4rem 2rem; text-align: center; }
    .hero h1 { font-size: clamp(2rem, 5vw, 3.5rem); }
  </style>

  <!-- DEFERRED: Full stylesheet loads asynchronously -->
  <link rel="preload" href="/styles.css" as="style" onload="this.onload=null;this.rel='stylesheet'">
  <noscript><link rel="stylesheet" href="/styles.css"></noscript>
</head>

Step 2: Defer Non-Critical CSS

<!-- Method 1: preload + onload swap -->
<link rel="preload" href="/styles.css" as="style"
      onload="this.onload=null;this.rel='stylesheet'">

<!-- Method 2: media attribute trick -->
<link rel="stylesheet" href="/styles.css" media="print"
      onload="this.media='all'">

<!-- Method 3: fetchpriority (modern browsers) -->
<link rel="stylesheet" href="/critical.css" fetchpriority="high">
<link rel="stylesheet" href="/non-critical.css" fetchpriority="low">

Extraction Tools

Manual Extraction (Small Sites)

For simple sites, manually identify above-the-fold styles:

  1. Load your page in Chrome DevTools
  2. Open Coverage tab (Ctrl+Shift+P, "Coverage")
  3. Note which CSS rules are used in the initial viewport
  4. Extract those into an inline <style> block

Automated Extraction

// Using Critical (Node.js tool)
const critical = require('critical');

critical.generate({
  base: 'dist/',
  src: 'index.html',
  css: ['dist/styles.css'],
  width: 1300,
  height: 900,
  inline: true,       // Inline critical CSS
  extract: true,      // Remove inlined rules from the external sheet
  target: 'index.html',
});

Framework-Level Solutions

// Next.js handles critical CSS automatically:
// - CSS Modules: Next.js inlines component CSS for the rendered route
// - Global CSS: Loaded normally but optimized via route-level splitting

// In next.config.ts
const config = {
  experimental: {
    optimizeCss: true, // Uses critters for automatic critical CSS
  },
};
Quiz
You defer a stylesheet using a preload link with an onload handler that swaps rel to stylesheet. What happens if JavaScript is disabled?
How Next.js optimizes CSS automatically

Next.js with the App Router does several optimizations:

  1. Route-level CSS splitting: CSS Modules only include styles for components in the current route — dead CSS from other routes isn't loaded.
  2. Automatic inlining: Small CSS chunks are inlined in the HTML response.
  3. Font optimization: next/font inlines font CSS and preloads font files.
  4. Streaming: CSS for streamed components arrives with their HTML chunks.

For most Next.js projects, manual critical CSS extraction isn't necessary — the framework handles it.

Measuring Impact

Key Metrics

MetricWithout Critical CSSWith Critical CSS
First Contentful Paint (FCP)Blocked by full CSS downloadImmediate — critical CSS inlined
Largest Contentful Paint (LCP)DelayedImproved if LCP element styles are inlined
Cumulative Layout Shift (CLS)Risk of FOUCMinimal if critical CSS is comprehensive

Chrome DevTools Measurement

1. Open DevTools → Performance tab
2. Throttle to "Slow 3G" or "Fast 3G"
3. Record page load
4. Compare FCP timing before/after critical CSS
5. Check Coverage tab for CSS utilization
Common Trap

If your critical CSS is incomplete (missing styles for above-the-fold content), users see a flash of unstyled content (FOUC) followed by a layout shift when the full CSS loads. This is worse than no critical CSS at all. Always test by throttling your network to 3G speeds and verifying the above-the-fold rendering looks correct with only the inlined styles.

Production Architecture

1. Build step generates route-specific CSS (CSS Modules + bundler)
2. Critical CSS extracted per route (critters or Critical)
3. Inlined in <head> of server-rendered HTML
4. Full route CSS loaded async with low priority
5. Subsequent navigations preload route CSS via <link rel="prefetch">
/* Route: /blog/[slug] — only these styles inlined */
.article-layout { display: grid; grid-template-columns: 1fr 300px; }
.article-header { margin-bottom: 2rem; }
.article-header h1 { font-size: var(--text-3xl); }
/* Sidebar, comments, footer styles load async */
Execution Trace
HTML arrives
Browser parses HTML with inlined `<style>`
Critical CSS available immediately
Critical paint
Above-the-fold content rendered
No network wait — styles are inline
Async load
`<link rel='preload'>` fetches full stylesheet
Non-blocking — happens in background
Full styles
Full stylesheet applied when loaded
Below-fold content now styled
CLS check
Above-fold styles unchanged — no layout shift
Critical CSS was comprehensive
What developers doWhat they should do
Inlining the entire stylesheet in `<head>`
Inlining 200KB of CSS bloats the HTML document and defeats the purpose. Inline only what's needed for the first viewport.
Only inline CSS for above-the-fold content. Defer the rest.
Not testing critical CSS on slow connections
On fast connections, the full CSS loads before you notice. On slow connections, missing critical CSS creates FOUC.
Always test with network throttling (3G) to verify above-the-fold renders correctly
Manually maintaining critical CSS as a separate file
Manual maintenance drifts out of sync as components change. Automated extraction stays accurate.
Use automated extraction tools (critters, Critical) in your build pipeline
Applying critical CSS extraction to a Next.js App Router project manually
The framework's built-in optimizations handle most cases. Manual extraction adds complexity without benefit.
Next.js handles CSS optimization automatically — route-level splitting and inlining
Quiz
Why is CSS render-blocking?
Quiz
What's the risk of incomplete critical CSS?
Key Rules
  1. 1CSS is render-blocking — inline critical (above-the-fold) CSS and defer the rest
  2. 2Use automated tools (critters, Critical) to extract critical CSS — don't maintain it manually
  3. 3Test with network throttling to verify above-the-fold rendering with only critical CSS
  4. 4Next.js handles CSS optimization automatically — manual extraction is rarely needed
  5. 5Incomplete critical CSS causes FOUC and CLS — always verify coverage