View contents
Critical CSS Guide: Extract, Inline, and Defer Styles
Introduction
Critical CSS is the minimal subset of styles needed to render the visible above-the-fold content without waiting for the site's entire stylesheet to download. Chrome explicitly recommends "extract critical CSS" and "defer non-critical CSS" as part of its audit for minimizing main thread work. This guide covers how to implement it step by step. The UXR SEO Analyzer flags when it detects full stylesheets blocking rendering instead of inlined critical CSS.
Why Critical CSS Matters
By default, the browser can't build the render tree until it processes all the CSS it finds in the <head>—this is part of the critical rendering path. If your main stylesheet weighs 200 KB but only 8 KB corresponds to what the user sees before scrolling, the browser still waits for the full 200 KB before painting anything. Inlining just those 8 KB and deferring the rest removes that block.
This connects directly to the LCP technical breakdown: when the LCP element is a text block or depends on the initial layout, a high render delay is usually caused by blocking CSS, not a slow image. Reducing that phase is exactly what well-implemented critical CSS achieves.
Step 1: Identify the Critical CSS
Critical CSS covers the styles that affect elements visible in the initial viewport, at the screen sizes that matter to you (mobile and desktop, given your project's single breakpoint). Automated tools like critical, penthouse, or beasties (the maintained successor to Critters) render the page in a headless browser and extract the CSS rules that actually apply to that viewport.
// build-critical-css.js — example using the "critical" package
const critical = require('critical');
await critical.generate({
inline: true,
base: 'dist/',
src: 'index.html',
target: 'index.html',
width: 1300,
height: 900,
dimensions: [
{ width: 375, height: 812 }, // mobile
{ width: 1300, height: 900 }, // desktop
],
});
Step 2: Inline the Critical CSS
The output gets injected directly into a <style> tag inside the <head>, avoiding an extra network request for the first paint:
<head>
<style>
/* Generated critical CSS, only ~8KB */
body { margin: 0; font-family: system-ui, sans-serif; }
.hero { min-height: 480px; background: #f5f5f5; }
.hero h1 { font-size: 2.5rem; }
</style>
</head>
Step 3: Defer the Non-Critical CSS
The rest of the stylesheet should load without blocking rendering. The standard pattern uses rel="preload" with a rel switch once it finishes loading, plus a <noscript> fallback:
<link rel="preload" href="/styles/main.css" as="style" onload="this.onload=null;this.rel='stylesheet'">
<noscript><link rel="stylesheet" href="/styles/main.css"></noscript>
This pattern downloads main.css at low, non-blocking priority and activates it as a real stylesheet once downloaded, without delaying first paint.
Tooling by Stack
| Stack | Recommended approach |
|---|---|
| Static sites / SSG | Extract critical CSS at build time with critical or beasties, per page |
| Vite / React / Vue | Build plugin that runs extraction over the generated HTML before deploying |
| Next.js | CSS-in-JS with automatic critical extraction, or an equivalent post-build step |
| WordPress and traditional CMS | Performance plugins that generate critical CSS per template |
Automating Extraction in Your Build Pipeline
Critical CSS goes stale every time you change the layout, so generating it by hand doesn't scale. The right approach is to make it a build step that runs on every deploy:
- The build generates the final HTML and CSS as usual
- A post-build script (using
critical,beasties, or equivalent) opens each generated page in a headless browser - The script extracts the CSS rules applied to the defined viewport and inlines them into each HTML file's
<head> - CI fails if the extraction step produces a block larger than the defined size budget (for example, 14KB)
For SSG sites with hundreds of pages, this step can be parallelized per page without significantly affecting build time, since each extraction is independent.
Critical CSS and Visual Stability (CLS)
One important side effect: if the critical CSS omits an above-the-fold element's dimensions (for example, an image container without aspect-ratio or min-height), the browser can paint that element with the wrong dimensions and then correct them once the full stylesheet arrives—producing exactly the kind of layout shift CLS penalizes. When building your critical CSS, verify it includes any rule that reserves space (dimensions, aspect-ratio, min-height) for elements visible above the fold, not just color and typography.
Common Mistakes
- Extracting too much "critical" CSS—if the inlined block exceeds ~14KB, it starts competing with the HTML itself for the initial TCP packet and loses part of its advantage
- Generating critical CSS for a single viewport only, ignoring the mobile layout most of your users will see
- Forgetting to regenerate critical CSS after design changes, leaving stale styles inlined
- Deferring styles that are actually critical, causing a visible flash of unstyled content (FOUC) once the full stylesheet applies
Relationship to Other Guides in This Hub
Critical CSS is just one of the resources that can block rendering. If Lighthouse still reports blocking resources after implementing it, the problem is likely synchronous scripts in the <head>—covered in Render-Blocking Resources Explained—or a slow TTFB delaying the HTML's arrival before any CSS, critical or not, can even apply.
Critical CSS Checklist
- [ ] Critical CSS extracted per template or page type, not a single generic block
- [ ] Inlined block under ~14KB compressed
- [ ] Dimensions and
aspect-ratiofor above-the-fold elements included in the critical block - [ ] Full stylesheet loaded with
rel="preload"+ switch tostylesheet, with a<noscript>fallback - [ ] Extraction automated as a build step, not a manual process
- [ ] Verified in Lighthouse that the full stylesheet no longer shows up as a blocking resource
How to Verify It
After implementing critical CSS, confirm in Chrome DevTools' Network panel that the main stylesheet downloads at low priority and no longer shows up in Lighthouse's "render-blocking resources" list. The UXR SEO Analyzer also flags this in its Performance tab.
Frequently Asked Questions
How much critical CSS is too much?
As a rule of thumb, keep the inlined block under 14KB compressed—the size of the initial TCP congestion window on most connections. More than that starts delaying the HTML instead of speeding it up.
Do I need different critical CSS per page?
On sites with very different templates (home, product, article), yes—each template has its own above-the-fold. On sites with a shared layout, a single critical block for the shell (header, navigation) is usually enough.
Does critical CSS replace minifying and compressing regular CSS?
No. They're complementary: minify and compress your entire stylesheet as a baseline practice, and additionally extract the critical subset for the first paint.
What if I use CSS-in-JS instead of separate stylesheets?
The principle is the same, but extraction changes: instead of parsing a .css file, the CSS-in-JS library needs native support for critical extraction on the server (rendering the page and collecting only the classes used). Several modern CSS-in-JS libraries include this capability; check your library's documentation before attempting to extract it manually.
Related Guides
References
- Chrome Developers - Minimize main thread work
- Chrome Developers - Eliminate render-blocking resources
- Chrome Developers - Avoid chaining critical requests
- web.dev - Core Web Vitals
