PatternsCore Optimizations
Critical CSS Inlining
Inline essential CSS for First Meaningful Paint and defer the rest to eliminate render-blocking resources.
Critical CSS Inlining
Problem
External CSS files block rendering. Users see a blank white screen until all CSS loads. For large applications with 100+ KB of CSS, this can take seconds on slow connections.
Solution
Identify and inline critical CSS (styles needed for above-the-fold content) directly in the <head>. Load the rest asynchronously after the page renders.
/**
* Extract critical CSS from full stylesheet
*/
interface CriticalCSSOptions {
html: string;
css: string;
width: number;
height: number;
}
async function extractCriticalCSS(options: CriticalCSSOptions): Promise<string> {
const { html, css, width, height } = options;
// Using critical package (production solution)
// This is a simplified example
const critical = await import('critical');
const result = await critical.generate({
inline: false,
base: '',
html,
css: [css],
width,
height,
penthouse: {
timeout: 30000,
},
});
return result.css;
}
/**
* Inline critical CSS in HTML
*/
function inlineCriticalCSS(html: string, criticalCSS: string): string {
const styleTag = `<style id="critical-css">${criticalCSS}</style>`;
return html.replace('</head>', `${styleTag}</head>`);
}
/**
* Load non-critical CSS asynchronously
*/
function createAsyncCSSLoader(href: string): HTMLLinkElement {
const link = document.createElement('link');
link.rel = 'stylesheet';
link.href = href;
link.media = 'print'; // Load with low priority
// Switch to all media once loaded
link.onload = () => {
link.media = 'all';
};
return link;
}
/**
* Preload CSS with low priority
*/
function preloadCSS(href: string): void {
const link = document.createElement('link');
link.rel = 'preload';
link.as = 'style';
link.href = href;
link.onload = function() {
const stylesheet = document.createElement('link');
stylesheet.rel = 'stylesheet';
stylesheet.href = href;
document.head.appendChild(stylesheet);
};
document.head.appendChild(link);
}
/**
* Load CSS based on media query
*/
function loadConditionalCSS(href: string, mediaQuery: string): void {
const link = document.createElement('link');
link.rel = 'stylesheet';
link.href = href;
link.media = mediaQuery;
document.head.appendChild(link);
}
// Practical implementation
/**
* CSS loading strategy manager
*/
class CSSLoadingStrategy {
private loadedStylesheets = new Set<string>();
/**
* Load critical CSS inline (server-side)
*/
public static generateCriticalCSS(html: string, fullCSS: string): Promise<string> {
return extractCriticalCSS({
html,
css: fullCSS,
width: 1300,
height: 900,
});
}
/**
* Load remaining CSS after critical
*/
public loadDeferredCSS(href: string): void {
if (this.loadedStylesheets.has(href)) {
return;
}
const link = createAsyncCSSLoader(href);
document.head.appendChild(link);
this.loadedStylesheets.add(href);
}
/**
* Load CSS when user interacts
*/
public loadOnInteraction(href: string, eventType: keyof WindowEventMap = 'scroll'): void {
const loadCSS = () => {
this.loadDeferredCSS(href);
window.removeEventListener(eventType, loadCSS);
};
window.addEventListener(eventType, loadCSS, { once: true, passive: true });
}
/**
* Load CSS when element enters viewport
*/
public loadOnVisible(href: string, targetSelector: string): void {
const target = document.querySelector(targetSelector);
if (!target) {
console.warn(`Target ${targetSelector} not found`);
return;
}
const observer = new IntersectionObserver(
(entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
this.loadDeferredCSS(href);
observer.disconnect();
}
});
},
{ rootMargin: '50px' }
);
observer.observe(target);
}
/**
* Load print styles only when needed
*/
public loadPrintStyles(href: string): void {
loadConditionalCSS(href, 'print');
}
}
// Build-time critical CSS extraction (Node.js)
const buildTimeCritical = `
import * as fs from 'fs';
import { generate } from 'critical';
async function generateCriticalCSS() {
const result = await generate({
inline: true,
base: 'dist/',
src: 'index.html',
target: {
html: 'index.html',
},
width: 1300,
height: 900,
dimensions: [
{ width: 375, height: 667 }, // Mobile
{ width: 1300, height: 900 }, // Desktop
],
});
fs.writeFileSync('dist/index.html', result.html);
}
generateCriticalCSS();
`;
// Usage in HTML
const htmlExample = `
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>My App</title>
<!-- Critical CSS inlined -->
<style id="critical-css">
/* Above-the-fold styles */
body { margin: 0; font-family: sans-serif; }
.header { height: 60px; background: #333; }
.hero { height: 400px; background: #f0f0f0; }
/* Only essential styles here */
</style>
<!-- Preload main stylesheet (but don't block render) -->
<link rel="preload" href="/styles/main.css" as="style" onload="this.onload=null;this.rel='stylesheet'">
<!-- Fallback for browsers without JS -->
<noscript><link rel="stylesheet" href="/styles/main.css"></noscript>
</head>
<body>
<header class="header">Header</header>
<main class="hero">Hero Section</main>
<script>
// Load deferred CSS after page load
if ('requestIdleCallback' in window) {
requestIdleCallback(() => {
const link = document.createElement('link');
link.rel = 'stylesheet';
link.href = '/styles/deferred.css';
document.head.appendChild(link);
});
} else {
window.addEventListener('load', () => {
const link = document.createElement('link');
link.rel = 'stylesheet';
link.href = '/styles/deferred.css';
document.head.appendChild(link);
});
}
</script>
</body>
</html>
`;Client-side Usage
// Initialize CSS loading strategy
const cssLoader = new CSSLoadingStrategy();
// Load main CSS after critical
document.addEventListener('DOMContentLoaded', () => {
cssLoader.loadDeferredCSS('/styles/main.css');
});
// Load component CSS when user scrolls
cssLoader.loadOnInteraction('/styles/footer.css', 'scroll');
// Load modal CSS when modal element is visible
cssLoader.loadOnVisible('/styles/modal.css', '#modal-trigger');
// Load print styles
cssLoader.loadPrintStyles('/styles/print.css');Webpack Plugin Configuration
// webpack.config.js
const HtmlWebpackPlugin = require('html-webpack-plugin');
const HtmlCriticalWebpackPlugin = require('html-critical-webpack-plugin');
module.exports = {
plugins: [
new HtmlWebpackPlugin({
template: 'src/index.html',
}),
new HtmlCriticalWebpackPlugin({
base: 'dist/',
src: 'index.html',
dest: 'index.html',
inline: true,
minify: true,
extract: true,
width: 1300,
height: 900,
penthouse: {
blockJSRequests: false,
},
}),
],
};Impact on Core Web Vitals
Before Critical CSS
- LCP: 3.5s (waiting for full CSS to load)
- FID: 250ms (render blocked)
- CLS: 0.15 (styles load late, layout shifts)
After Critical CSS
- LCP: 1.2s (content renders immediately)
- FID: 50ms (no render blocking)
- CLS: 0.02 (critical styles prevent layout shift)
Performance gain: 65% faster First Meaningful Paint
Best Practices
- Keep critical CSS small: Under 14KB (fits in first TCP packet)
- Update regularly: Critical CSS should match current layout
- Test on real devices: Use Lighthouse with throttling
- Inline only critical: Everything else loads async
- Use CSS variables: Reduce duplication in critical CSS
Tools
- critical: Node.js library for critical CSS extraction
- critters: Webpack plugin (used by Next.js)
- penthouse: Headless browser critical CSS generator
- PurgeCSS: Remove unused CSS before extraction
Automation
// GitHub Actions workflow
const githubAction = `
name: Generate Critical CSS
on: [push]
jobs:
critical-css:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Generate Critical CSS
run: |
npm install
npm run build
npm run critical-css
`;Critical CSS inlining is essential for apps serving millions of users on varying network conditions. It's the difference between a 1-second load and a 5-second load on 3G.