vite-plugin-critical-css
v1.0.0
Published
Vite plugin that extracts and inlines critical (above-the-fold) CSS into the <head> and defers the rest for improved LCP and Core Web Vitals.
Maintainers
Readme
EN | FR
BrowserUX Critical CSS
Eliminate render-blocking CSS and dramatically improve your LCP score.
BrowserUX Critical CSS is a Vite plugin that automatically extracts the CSS needed to render above-the-fold content, inlines it directly in the <head> as a <style> tag, and defers the full stylesheet asynchronously using the <link rel="preload"> pattern. The extraction uses a headless browser with configurable viewport dimensions, making it accurate across both mobile and desktop breakpoints. The result is a page that renders instantly without waiting for any CSS to download.
Features
- 🚀 Inlines critical (above-the-fold) CSS directly in
<head>for instant first paint - ⚡ Eliminates render-blocking stylesheets for a dramatic LCP improvement
- 📱 Multi-viewport analysis, extracts CSS for both mobile and desktop in one pass
- 🔁 Defers non-critical CSS with
<link rel="preload" as="style" onload>and a<noscript>fallback - 📄 Processes all HTML files in the output directory automatically
- 🔍 Supports glob patterns to target specific pages
- 🎯 Force-includes CSS selectors that are only active after JavaScript runs (e.g. theme switchers)
- ⏱️ Configurable headless browser timeout per page
- 📦 Optionally logs per-file processing results
- 🛠️ Powered by penthouse, direct headless extraction, no temp-file intermediary
- 🖥️ Compatible with WSL2, uses a local HTTP server instead of
file://URLs, so Windows Chrome can load your pages
Installation
npm install vite-plugin-critical-css --save-devNote: This plugin uses a headless Chromium browser (via Puppeteer) to accurately determine above-the-fold content. Chromium is downloaded automatically the first time.
WSL2 users: the plugin automatically spins up a local HTTP server so that the Windows-native Chrome process can load your built pages over
http://127.0.0.1instead of inaccessiblefile://paths.
Usage
import { defineConfig } from 'vite'
import criticalCss from 'vite-plugin-critical-css'
export default defineConfig({
plugins: [
criticalCss({
dimensions: [
{ width: 375, height: 812 }, // Mobile
{ width: 1300, height: 900 }, // Desktop
],
pages: ['**/*.html'],
inline: true,
deferStylesheets: true,
forceInclude: [],
timeout: 30000,
logListings: true,
})
]
})The plugin only runs during vite build. It has no effect in development mode.
What it produces
For each HTML file, the plugin replaces this:
<head>
<link rel="stylesheet" href="/assets/main.a1b2c3.css">
</head>With this:
<head>
<!-- Above-the-fold CSS inlined, no network request needed for first paint -->
<style>
/* critical CSS extracted from main.a1b2c3.css */
body { margin: 0; font-family: sans-serif; }
.hero { display: flex; min-height: 100vh; }
/* ... */
</style>
<!-- Full stylesheet loaded asynchronously, does not block rendering -->
<link rel="preload" href="/assets/main.a1b2c3.css" as="style" onload="this.onload=null;this.rel='stylesheet'">
<noscript><link rel="stylesheet" href="/assets/main.a1b2c3.css"></noscript>
</head>Options
| Option | Type | Default | Description |
| --- | --- | --- | --- |
| dimensions | Dimension[] | [{ width: 375, height: 812 }, { width: 1300, height: 900 }] | Viewport dimensions used to determine above-the-fold content. CSS for all viewports is merged. |
| pages | string[] | ['**/*.html'] | Glob patterns (relative to the output directory) of HTML files to process. |
| inline | boolean | true | Inline the critical CSS and defer non-critical CSS. Set to false to skip HTML modification. |
| deferStylesheets | boolean | true | Convert <link rel="stylesheet"> to async preload after inlining critical CSS. Set to false if your site uses JS-driven CSS selectors (e.g. [data-theme=dark]) that must apply synchronously. |
| forceInclude | (string \| RegExp)[] | [] | CSS selectors to always inline regardless of above-the-fold visibility. Use this for selectors activated by JavaScript at runtime (e.g. ['[data-theme="dark"]'] for a theme switcher). |
| timeout | number | 30000 | Maximum time in milliseconds to wait for headless browser analysis per page. |
| logListings | boolean | true | Log per-file results in the terminal after the build. |
TypeScript interfaces
interface CriticalCssOptions {
dimensions?: Dimension[]
pages?: string[]
inline?: boolean
deferStylesheets?: boolean
forceInclude?: (string | RegExp)[]
timeout?: number
logListings?: boolean
}
interface Dimension {
width: number // Viewport width in pixels
height: number // Viewport height in pixels
}Documentation
Guide
- Introduction, what the plugin does, when to use it, features overview
- Getting started, installation and minimal configuration
- How it works, headless browser analysis, HTML transformation, deferred loading
- Multi-page applications, processing multiple HTML files, glob patterns
- CI / Docker environments, running in CI, Docker, and headless environments
Reference
Additional
- Compatibility, Node.js, Vite version, and dependency matrix
- Contributing, how to report issues and submit pull requests
- Changelog
