Critical CSS Inlining — Eliminate Render-Blocking CSS for Faster Load
Critical CSS inlining embeds above-the-fold styles directly in HTML to eliminate render-blocking requests. Learn how to extract critical CSS with tools like critical,...
Use the tool
HTML / CSS / JS Minifier
Minify HTML, CSS, or JavaScript. Strips whitespace, comments, and unnecessary characters. Shows size reduction percentage.
Render-blocking CSS delays the first paint: the browser must download and parse your stylesheet before drawing anything. Inlining critical CSS (above-the-fold styles) unblocks rendering and improves LCP.
Minify your HTML and CSS with the HTML Minifier.
What is critical CSS?
Without critical CSS inlining:
1. Browser receives HTML
2. Sees <link rel="stylesheet" href="styles.css">
3. BLOCKS rendering while downloading styles.css (100-500ms on 3G)
4. Parses CSS
5. Finally renders the page
With critical CSS inlining:
1. Browser receives HTML with <style>/* above-fold styles */</style>
2. Renders immediately (no blocking request needed)
3. Fetches full styles.css asynchronously in background
4. Loads non-critical styles without blocking
Manual approach: identify and inline critical styles
<!DOCTYPE html>
<html>
<head>
<!-- Critical CSS inlined in <style> -->
<style>
/* Only styles needed for above-the-fold content */
body { margin: 0; font-family: sans-serif; }
header { background: #1e293b; color: white; padding: 1rem; }
.hero { min-height: 60vh; display: flex; align-items: center; }
.hero h1 { font-size: 2.5rem; margin: 0; }
.nav { display: flex; gap: 1rem; list-style: none; }
</style>
<!-- Load full CSS non-blocking (preload trick): -->
<link rel="preload" href="/styles.css" as="style" onload="this.onload=null;this.rel='stylesheet'">
<noscript><link rel="stylesheet" href="/styles.css"></noscript>
</head>
<body>
<!-- ... page content ... -->
</body>
</html>
Automated extraction: critical npm package
npm install -D critical
// generate-critical-css.mjs
import critical from 'critical';
await critical.generate({
inline: true, // Inline critical CSS into HTML
base: 'dist/',
src: 'index.html',
target: 'index.html', // Overwrite with inlined version
dimensions: [
{ width: 375, height: 812 }, // Mobile (iPhone SE)
{ width: 768, height: 1024 }, // Tablet
{ width: 1440, height: 900 }, // Desktop
],
penthouse: {
timeout: 60000,
},
});
console.log('Critical CSS generated and inlined');
The critical package:
- Opens the page in a headless browser at each viewport
- Identifies which CSS rules apply to visible content
- Inlines those rules in
<style>tags - Converts the
<link rel="stylesheet">to lazy-load
Critters (Google’s tool, lighter than critical)
npm install -D critters
// Webpack: critters-webpack-plugin
import Critters from 'critters-webpack-plugin';
export default {
plugins: [
new Critters({
preload: 'swap', // Use font-display: swap for fonts
pruneSource: true, // Remove inlined styles from main CSS
logLevel: 'warn',
}),
],
};
// Vite: vite-plugin-critical
import { critical } from 'vite-plugin-critical';
export default {
plugins: [
critical({
criticalUrl: 'http://localhost:4173',
criticalBase: 'dist',
criticalPages: [
{ uri: '/', template: 'index' },
{ uri: '/about', template: 'about' },
],
criticalConfig: {
dimensions: [{ width: 1300, height: 900 }],
},
}),
],
};
Lazy-load remaining CSS without JavaScript
<!-- The preload + onload trick (works without JS for print media): -->
<link rel="stylesheet" media="print" onload="this.media='all'" href="/styles.css">
<noscript>
<link rel="stylesheet" href="/styles.css">
</noscript>
<!-- Or with rel="preload" as="style": -->
<link rel="preload" href="/styles.css" as="style">
<link rel="stylesheet" href="/styles.css" media="(prefers-color-scheme: dark)">
<!-- Actually: just use the preload trick above, it's simpler -->
How much CSS to inline
Too little: missing styles cause layout shift (CLS)
Too much: inline CSS > ~14KB defeats the purpose
(browsers can't cache inline styles)
Rule of thumb:
- 2-5KB of inline CSS is ideal
- Max ~10KB before inlining costs more than it saves
- Only styles needed for the first viewport (hero, header, nav)
// Measure critical CSS size:
const fs = require('fs');
const criticalCss = fs.readFileSync('dist/critical.css', 'utf8');
const gzipped = require('zlib').gzipSync(criticalCss).length;
console.log(`Critical CSS: ${criticalCss.length} bytes, gzipped: ${gzipped} bytes`);
// Aim for: < 5KB uncompressed, < 2KB gzipped
Combined with HTML minification
<!-- Before: -->
<style>
/* lots of whitespace */
body {
margin: 0;
padding: 0;
}
</style>
<!-- After HTML minification: -->
<style>body{margin:0;padding:0}</style>
Use the HTML Minifier to minify the inlined style block along with the rest of the HTML.
Express: serve pre-generated critical CSS HTML
import express from 'express';
import { readFileSync } from 'fs';
const app = express();
// Serve pre-generated HTML with critical CSS inlined:
const indexHTML = readFileSync('dist/index.html', 'utf-8');
app.get('/', (req, res) => {
res.set('Content-Type', 'text/html');
res.set('Cache-Control', 'no-cache'); // HTML always fresh
res.send(indexHTML);
});
Related tools
- HTML Minifier — minify HTML including inlined styles
- Image Compressor — reduce image sizes for faster LCP
- CSS Grid Generator — generate layout CSS
Related posts
- GZIP Compression for HTML — How It Works and How to Enable It — GZIP compression reduces HTML file size by 60-80% before sending to browsers. Le…
- HTML Comments Guide — Syntax, Uses, and When Minifiers Remove Them — HTML comments use <!-- --> syntax and are removed by minifiers unless they're co…
- HTML Minification Explained — What Gets Removed and Why — HTML minification removes whitespace, comments, and redundant attributes to redu…
- HTML Minification — What It Does and How Much It Saves — HTML minification removes whitespace, comments, and redundant attributes to redu…
Related tool
HTML / CSS / JS Minifier
Minify HTML, CSS, or JavaScript. Strips whitespace, comments, and unnecessary characters. Shows size reduction percentage.
Written by Mian Ali Khalid. Part of the Frontend & Design pillar.