Skip to main content

Critical CSS and Performance Optimization

Answer

Critical CSS is the minimum CSS required to render above-the-fold content. Extracting and inlining it improves First Contentful Paint (FCP) by eliminating render-blocking stylesheets.

The Problem

The Solution

Implementing Critical CSS

<!DOCTYPE html>
<html>
<head>
<!-- Critical CSS inlined -->
<style>
/* Only styles needed for above-the-fold content */
body {
margin: 0;
font-family: system-ui, sans-serif;
}
.header {
height: 60px;
background: #333;
color: white;
display: flex;
align-items: center;
padding: 0 20px;
}
.hero {
min-height: 400px;
background: linear-gradient(135deg, #667eea, #764ba2);
display: flex;
align-items: center;
justify-content: center;
}
.hero h1 {
color: white;
font-size: 3rem;
}
</style>

<!-- Full CSS loaded 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>
<body>
<!-- Above-the-fold content -->
<header class="header">Logo</header>
<section class="hero">
<h1>Welcome</h1>
</section>

<!-- Below-the-fold content -->
<!-- ... -->
</body>
</html>

Automated Critical CSS Extraction

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

critical.generate({
base: "dist/",
src: "index.html",
target: {
html: "index-critical.html",
css: "critical.css",
},
width: 1300,
height: 900,
inline: true,
});
// Webpack with critters
// webpack.config.js
const Critters = require("critters-webpack-plugin");

module.exports = {
plugins: [
new Critters({
preload: "swap",
pruneSource: true,
}),
],
};

CSS Loading Strategies

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

<!-- 2. Media query trick (downloads but doesn't block) -->
<link
rel="stylesheet"
href="styles.css"
media="print"
onload="this.media='all'"
/>

<!-- 3. loadCSS library -->
<script>
!function(e){"use strict";var t=function(t,n,r,o){...}(window)
</script>
<noscript><link rel="stylesheet" href="styles.css" /></noscript>

<!-- 4. Dynamic import -->
<script>
const link = document.createElement("link");
link.rel = "stylesheet";
link.href = "styles.css";
document.head.appendChild(link);
</script>

Code Splitting CSS

// Webpack CSS extraction per entry
module.exports = {
entry: {
main: "./src/index.js",
about: "./src/about.js",
},
plugins: [
new MiniCssExtractPlugin({
filename: "[name].[contenthash].css",
}),
],
};

// Lazy-loaded routes can have their own CSS
// React with code splitting
const About = React.lazy(() => import("./About"));
// About.css only loads when About component is needed

Removing Unused CSS

// PurgeCSS configuration
// purgecss.config.js
module.exports = {
content: ["./src/**/*.html", "./src/**/*.js", "./src/**/*.jsx"],
css: ["./src/**/*.css"],
safelist: [
/^modal-/, // Keep classes matching pattern
"active",
],
output: "./dist/",
};
// With Tailwind CSS (built-in purging)
// tailwind.config.js
module.exports = {
content: [
"./pages/**/*.{js,ts,jsx,tsx}",
"./components/**/*.{js,ts,jsx,tsx}",
],
// Unused utilities are removed in production
};

Measuring CSS Performance

// Performance API
const cssEntries = performance
.getEntriesByType("resource")
.filter(
(entry) => entry.initiatorType === "link" || entry.name.endsWith(".css")
);

cssEntries.forEach((entry) => {
console.log(`${entry.name}:`, {
size: entry.transferSize,
duration: entry.duration,
blocked: entry.renderBlockingStatus,
});
});

// Web Vitals
import { getCLS, getFID, getLCP } from "web-vitals";

getCLS(console.log);
getFID(console.log);
getLCP(console.log);

Performance Checklist

Optimization Summary

TechniqueImpactEffort
Critical CSSHighMedium
Remove unusedHighLow
Async loadingMediumLow
MinificationMediumLow
Code splittingMediumMedium
Gzip/BrotliMediumLow
CDN cachingHighLow

Tools

  • Critical: Critical CSS extraction
  • PurgeCSS: Remove unused CSS
  • cssnano: Minification
  • Critters: Webpack plugin for critical CSS
  • Coverage (DevTools): Find unused CSS
  • Lighthouse: Performance auditing

Key Points

  • Extract and inline above-the-fold CSS
  • Load remaining CSS asynchronously
  • Remove unused CSS with PurgeCSS
  • Use tools like Critical, Critters automatically
  • Measure impact with DevTools and Web Vitals
  • Critical CSS should be ~14KB or less (ideal)