Inline critical CSS for faster rendering
Critical CSS (above-the-fold content) is inlined in the head for faster initial render.
- Inline ~14KB of critical above-the-fold CSS in <head>
- Use tools: critical, critters, or Lighthouse to extract
- Load remaining CSS asynchronously after page load
- Automate in build process—don't manually maintain
Rule Details
Critical CSS contains the minimum styles needed to render above-the-fold content, improving page load performance by reducing render-blocking resources.
Code Example
<head>
<!-- Critical CSS inlined in the head -->
<style>
/* Critical styles for above-the-fold content */
body {
font-family: -apple-system, BlinkMacSystemFont, sans-serif;
margin: 0;
line-height: 1.6;
}
.header {
background: #333;
color: white;
padding: 1rem;
position: sticky;
top: 0;
}
.hero {
min-height: 50vh;
display: flex;
align-items: center;
justify-content: center;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
text-align: center;
}
.hero h1 {
font-size: 2.5rem;
margin: 0 0 1rem 0;
}
.cta-button {
background: #ff6b6b;
color: white;
border: none;
padding: 1rem 2rem;
border-radius: 5px;
font-size: 1.1rem;
cursor: pointer;
}
</style>
<!-- Non-critical CSS loaded asynchronously -->
<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>
</head>Why It Matters
Critical CSS eliminates render-blocking resources, enabling browsers to paint content immediately—directly improving LCP and FCP scores.
Framework Implementations
Next.js Critical CSS
// pages/_document.js
import Document, { Html, Head, Main, NextScript } from 'next/document'
class MyDocument extends Document {
render() {
return (
<Html>
<Head>
{/* Critical CSS for above-the-fold content */}
<style dangerouslySetInnerHTML={{
__html: `
body {
font-family: -apple-system, BlinkMacSystemFont, sans-serif;
margin: 0;
line-height: 1.6;
}
.header {
background: #333;
color: white;
padding: 1rem;
position: sticky;
top: 0;
}
.hero {
min-height: 50vh;
display: flex;
align-items: center;
justify-content: center;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
}
.loading {
display: flex;
justify-content: center;
padding: 2rem;
}
`
}} />
</Head>
<body>
<Main />
<NextScript />
</body>
</Html>
)
}
}
export default MyDocumentNext.js with CSS-in-JS Critical Path
// components/CriticalStyles.js
import { Global, css } from '@emotion/react'
export const CriticalStyles = () => (
<Global
styles={css`
/* Critical styles that must render immediately */
body {
font-family: -apple-system, BlinkMacSystemFont, sans-serif;
margin: 0;
line-height: 1.6;
background-color: #fff;
}
/* Layout styles for above-the-fold */
.container {
max-width: 1200px;
margin: 0 auto;
padding: 0 1rem;
}
/* Header that's always visible */
.site-header {
background: #333;
color: white;
position: sticky;
top: 0;
z-index: 100;
}
/* Hero section styles */
.hero {
min-height: 60vh;
display: flex;
align-items: center;
justify-content: center;
text-align: center;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
}
/* Critical typography */
h1, h2, h3 {
line-height: 1.2;
margin: 0 0 1rem 0;
}
/* Essential interactive elements */
button, .btn {
background: #007bff;
color: white;
border: none;
padding: 0.75rem 1.5rem;
border-radius: 4px;
cursor: pointer;
transition: background-color 0.2s;
}
button:hover, .btn:hover {
background: #0056b3;
}
`}
/>
)
// pages/_app.js
import { CriticalStyles } from '../components/CriticalStyles'
function MyApp({ Component, pageProps }) {
return (
<>
<CriticalStyles />
<Component {...pageProps} />
</>
)
}
export default MyAppReact with Critical CSS Hook
import { useEffect, useState } from 'react'
function useCriticalCSS() {
const [criticalLoaded, setCriticalLoaded] = useState(false)
useEffect(() => {
// Inject critical CSS
const criticalStyles = `
body {
font-family: system-ui, sans-serif;
margin: 0;
line-height: 1.6;
}
.app-header {
background: #000;
color: #fff;
padding: 1rem;
}
.hero {
min-height: 50vh;
display: flex;
align-items: center;
justify-content: center;
}
.loading {
text-align: center;
padding: 2rem;
}
`
const styleElement = document.createElement('style')
styleElement.textContent = criticalStyles
document.head.appendChild(styleElement)
setCriticalLoaded(true)
// Load non-critical CSS
const link = document.createElement('link')
link.rel = 'stylesheet'
link.href = '/styles/non-critical.css'
document.head.appendChild(link)
return () => {
document.head.removeChild(styleElement)
}
}, [])
return criticalLoaded
}
function App() {
const criticalLoaded = useCriticalCSS()
if (!criticalLoaded) {
return <div className="loading">Loading...</div>
}
return (
<div className="app">
<header className="app-header">
<h1>My App</h1>
</header>
<main className="hero">
<div>
<h2>Welcome to our app</h2>
<button>Get Started</button>
</div>
</main>
</div>
)
}Vue.js Critical CSS Strategy
<!-- App.vue -->
<template>
<div id="app">
<AppHeader />
<router-view />
<AppFooter />
</div>
</template>
<script>
export default {
name: 'App',
mounted() {
// Load non-critical CSS after mount
this.loadNonCriticalCSS()
},
methods: {
loadNonCriticalCSS() {
const nonCriticalFiles = [
'/css/components.css',
'/css/animations.css',
'/css/responsive-extended.css'
]
nonCriticalFiles.forEach(href => {
const link = document.createElement('link')
link.rel = 'stylesheet'
link.href = href
link.onload = () => console.log(`Loaded: ${href}`)
document.head.appendChild(link)
})
}
}
}
</script>
<style>
/* Critical CSS embedded in component */
#app {
font-family: -apple-system, BlinkMacSystemFont, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
margin: 0;
}
/* Critical layout styles */
.container {
max-width: 1200px;
margin: 0 auto;
padding: 0 1rem;
}
/* Above-the-fold header */
.app-header {
background: linear-gradient(90deg, #667eea 0%, #764ba2 100%);
color: white;
padding: 1rem 0;
position: sticky;
top: 0;
z-index: 1000;
}
/* Hero section that's immediately visible */
.hero-section {
min-height: 60vh;
display: flex;
align-items: center;
justify-content: center;
text-align: center;
background: #f8f9fa;
}
/* Critical interactive elements */
.btn-primary {
background: #007bff;
color: white;
border: none;
padding: 1rem 2rem;
border-radius: 8px;
font-size: 1.1rem;
cursor: pointer;
transition: all 0.2s ease;
}
.btn-primary:hover {
background: #0056b3;
transform: translateY(-2px);
}
/* Loading states */
.loading {
display: flex;
justify-content: center;
align-items: center;
min-height: 200px;
}
</style>Build Tool Integration
Webpack Critical CSS Plugin
// webpack.config.js
const HtmlCriticalWebpackPlugin = require('html-critical-webpack-plugin')
module.exports = {
plugins: [
new HtmlCriticalWebpackPlugin({
base: path.resolve(__dirname, 'dist'),
src: 'index.html',
dest: 'index.html',
inline: true,
minify: true,
extract: true,
width: 1200,
height: 800,
penthouse: {
blockJSRequests: false,
}
})
]
}Vite Critical CSS
// vite.config.js
import { defineConfig } from 'vite'
import { ViteEjsPlugin } from 'vite-plugin-ejs'
export default defineConfig({
plugins: [
ViteEjsPlugin({
criticalCSS: `
body { font-family: system-ui, sans-serif; margin: 0; }
.header { background: #333; color: white; padding: 1rem; }
.hero { min-height: 50vh; display: flex; align-items: center; }
`
})
],
build: {
cssCodeSplit: true,
rollupOptions: {
output: {
manualChunks: {
critical: ['./src/styles/critical.css'],
vendor: ['./src/styles/vendor.css'],
components: ['./src/styles/components.css']
}
}
}
}
})PostCSS Critical CSS
// postcss.config.js
module.exports = {
plugins: [
require('@fullhuman/postcss-purgecss')({
content: ['./src/**/*.html', './src/**/*.js'],
safelist: ['critical-*', 'above-fold-*']
}),
require('cssnano')({
preset: 'default'
})
]
}Automated Critical CSS Generation
Critical CSS with Puppeteer
// generate-critical.js
const puppeteer = require('puppeteer')
const critical = require('critical')
const fs = require('fs').promises
async function generateCriticalCSS() {
const browser = await puppeteer.launch()
const page = await browser.newPage()
// Set viewport to common desktop size
await page.setViewport({ width: 1200, height: 800 })
// Navigate to your page
await page.goto('http://localhost:3000', { waitUntil: 'networkidle2' })
// Generate critical CSS
const criticalCSS = await critical.generate({
inline: false,
base: 'dist/',
src: 'index.html',
width: 1200,
height: 800,
minify: true
})
// Save critical CSS
await fs.writeFile('src/styles/critical.css', criticalCSS.css)
await browser.close()
console.log('Critical CSS generated successfully!')
}
generateCriticalCSS()GitHub Actions Critical CSS Workflow
# .github/workflows/critical-css.yml
name: Generate Critical CSS
on:
push:
branches: [main]
paths: ['src/styles/**', 'src/components/**']
jobs:
generate-critical:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: '18'
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Build application
run: npm run build
- name: Start server
run: npm start &
- name: Wait for server
run: sleep 10
- name: Generate critical CSS
run: npm run generate:critical
- name: Commit critical CSS
run: |
git config --local user.email "action@github.com"
git config --local user.name "GitHub Action"
git add src/styles/critical.css
git commit -m "Update critical CSS" || exit 0
git pushPerformance Monitoring
Core Web Vitals Tracking
// Track critical CSS impact on performance
function measureCriticalCSSPerformance() {
// Monitor First Contentful Paint
new PerformanceObserver((entryList) => {
for (const entry of entryList.getEntries()) {
if (entry.name === 'first-contentful-paint') {
console.log('FCP:', entry.startTime)
// Send to analytics
gtag('event', 'timing_complete', {
name: 'critical_css_fcp',
value: Math.round(entry.startTime)
})
}
}
}).observe({ type: 'paint', buffered: true })
// Monitor Largest Contentful Paint
new PerformanceObserver((entryList) => {
const entries = entryList.getEntries()
const lastEntry = entries[entries.length - 1]
console.log('LCP:', lastEntry.startTime)
gtag('event', 'timing_complete', {
name: 'critical_css_lcp',
value: Math.round(lastEntry.startTime)
})
}).observe({ type: 'largest-contentful-paint', buffered: true })
}
// Initialize monitoring
measureCriticalCSSPerformance()A/B Testing Critical CSS
// Test different critical CSS strategies
function initializeCriticalCSSTest() {
const testGroup = Math.random() < 0.5 ? 'minimal' : 'extended'
if (testGroup === 'minimal') {
// Load minimal critical CSS
loadCriticalCSS('/styles/critical-minimal.css')
} else {
// Load extended critical CSS
loadCriticalCSS('/styles/critical-extended.css')
}
// Track the test group
gtag('config', 'GA_MEASUREMENT_ID', {
custom_map: { custom_dimension_1: 'critical_css_test' }
})
gtag('event', 'experiment_impression', {
custom_dimension_1: testGroup
})
}
function loadCriticalCSS(href) {
const link = document.createElement('link')
link.rel = 'stylesheet'
link.href = href
link.onload = () => {
document.documentElement.classList.add('critical-css-loaded')
}
document.head.appendChild(link)
}Tools and Resources
- Critical: npm package by Addy Osmani for generating critical CSS
- Penthouse: Critical path CSS generator
- UnCSS: Remove unused CSS from your stylesheets
- PurgeCSS: Remove unused CSS for smaller bundles
- Lighthouse: Audit critical CSS implementation
- WebPageTest: Measure critical CSS impact on performance
Best Practices
- Keep it minimal: Only include styles for above-the-fold content
- Inline critical CSS: Embed in
<style>tags to avoid extra HTTP requests - Load non-critical asynchronously: Use
rel="preload"withonloadhandler - Minify critical CSS: Remove comments, whitespace, and unused properties
- Test on real devices: Verify critical CSS works across different screen sizes
- Monitor performance: Track Core Web Vitals to measure improvements
- Update regularly: Regenerate critical CSS when designs change
- Use build tools: Automate critical CSS generation in your build process
Verification
Automated Checks
- Confirm the computed styles match the intended fix in DevTools.
- If the rule affects motion, contrast, or layout stability, verify those user-facing outcomes directly.
Manual Checks
- Inspect the rendered UI at the breakpoints and interaction states affected by the rule.
- Test at least one mobile and one desktop viewport before shipping.
Use with AI
Copy these prompts to use with your AI assistant, or install the MCP server to use directly from Claude, Cursor, or Windsurf.
Check
Verify implementation
Analyze this page's CSS loading strategy to identify critical above-the-fold styles that should be inlined and non-critical styles that can be loaded asynchronously.
Fix
Auto-fix issues
Extract and inline critical CSS for above-the-fold content, then load remaining CSS asynchronously to improve initial page render performance.
Explain
Learn more
Explain how critical CSS improves Core Web Vitals by reducing render-blocking resources and enabling faster First Contentful Paint and Largest Contentful Paint.
Review
Code review
Review stylesheets, component styles, and responsive states related to Inline critical CSS for faster rendering. Flag exact selectors, declarations, or breakpoints that violate the rule in the rendered UI.