Critical CSS and Extraction
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.
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:
- Load your page in Chrome DevTools
- Open Coverage tab (Ctrl+Shift+P, "Coverage")
- Note which CSS rules are used in the initial viewport
- 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
},
};
How Next.js optimizes CSS automatically
Next.js with the App Router does several optimizations:
- Route-level CSS splitting: CSS Modules only include styles for components in the current route — dead CSS from other routes isn't loaded.
- Automatic inlining: Small CSS chunks are inlined in the HTML response.
- Font optimization:
next/fontinlines font CSS and preloads font files. - 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
| Metric | Without Critical CSS | With Critical CSS |
|---|---|---|
| First Contentful Paint (FCP) | Blocked by full CSS download | Immediate — critical CSS inlined |
| Largest Contentful Paint (LCP) | Delayed | Improved if LCP element styles are inlined |
| Cumulative Layout Shift (CLS) | Risk of FOUC | Minimal 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
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 */
| What developers do | What 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 |
- 1CSS is render-blocking — inline critical (above-the-fold) CSS and defer the rest
- 2Use automated tools (critters, Critical) to extract critical CSS — don't maintain it manually
- 3Test with network throttling to verify above-the-fold rendering with only critical CSS
- 4Next.js handles CSS optimization automatically — manual extraction is rarely needed
- 5Incomplete critical CSS causes FOUC and CLS — always verify coverage