Page speed, redirects, Core Web Vitals
Remove unused CSS to speed up first paint
Unused CSS bloats render-blocking stylesheets and slows FCP. Use Coverage tools to find dead rules and split styles by route to ship only what each page needs.
What's happening
Lighthouse's "Reduce unused CSS" audit flags stylesheets where most of the bytes downloaded never apply to the rendered page. A typical Bootstrap or legacy theme site ships 200-400KB of CSS where 80%+ is dead code on any given route. Because CSS is render-blocking, every unused byte delays FCP and LCP.
Chrome DevTools' Coverage tab (Cmd+Shift+P → Show Coverage) records which CSS rules execute as the page loads and interacts. Anything in the "Unused" column on idle is dead weight on this route. PurgeCSS, Tailwind's JIT compiler, and Next.js's CSS extraction can remove most of it at build time.
The problem compounds with global CSS frameworks shipped on every route. Even with HTTP/2 and gzip, parsing 300KB of CSS adds 100-200ms on mid-tier mobile devices. The CSSOM construction is single-threaded and CPU-bound.
Why it matters
Unused CSS slows FCP, which constrains LCP, which directly fails Core Web Vitals. Pages serving 200KB+ of mostly-unused CSS routinely fail the LCP threshold at the 75th percentile on mobile.
User-facing, the impact is a longer blank-screen period. The browser holds rendering until all blocking CSS arrives and parses. Reducing unused CSS by even 50KB shaves 100-300ms off mobile FCP, which moves LCP by the same amount.
Common causes
- Global CSS frameworks (Bootstrap, legacy theme bundles) loaded on every route.
- Component libraries shipped without tree-shaking (importing all of Material UI's CSS).
- Dead CSS left over from removed features that nobody pruned.
- CSS-in-JS libraries (styled-components, emotion) that ship runtime-injected styles for unused components.
- Per-route CSS bundling not enabled in the build.
- @import chains that pull in shared partials wholesale.
Detect this on your site
Run a quick scan with the Speed Test. The tool surfaces this exact issue with the records and context needed to apply the fix below.
Open Speed TestHow to fix it
- 1
Audit with Chrome DevTools Coverage
Open DevTools, hit Cmd+Shift+P, type "Show Coverage," reload the page. The Coverage panel shows every CSS file with a usage percentage. Anything below 30% is a strong candidate for splitting or removal.
- 2
Migrate to a utility-first or atomic CSS framework
Tailwind v4's JIT compiler ships only the utility classes you actually use. Most production sites end up with 15-30KB of CSS regardless of how big the app is. Migration is incremental — add Tailwind alongside the legacy CSS, port routes one at a time, then delete the old framework.
- 3
Enable PurgeCSS or content-aware extraction
PurgeCSS scans your templates, finds class names actually used, and strips the rest. It runs at build time so there's no runtime cost. Configure the content array carefully — false positives in template scanning are the most common breakage source.
- 4
Split CSS by route
Next.js with CSS Modules or App Router automatically splits CSS per route. Each page only loads its own component CSS plus the global CSS. Verify in DevTools Coverage that route-specific CSS doesn't bleed into other routes.
- 5
Audit and remove unused @media queries
Print stylesheets, ancient device-targeting media queries, and unused breakpoints can ship hundreds of bytes per rule. Search the codebase for @media (-webkit-min-device-pixel-ratio: 1.5) and similar legacy patterns.
- 6
Eliminate runtime CSS-in-JS overhead
Runtime CSS-in-JS (emotion, styled-components without compile-time extraction) ships JavaScript that generates CSS at runtime. Migrate to compile-time extraction (linaria, vanilla-extract, Pigment CSS) or to Tailwind for zero runtime cost.
- 7
Validate with Lighthouse Coverage
After cleanup, re-run Lighthouse and check the "Reduce unused CSS" opportunity. The savings should drop substantially. Track this in CI with Lighthouse CI to catch regressions when someone adds a new CSS framework dependency.
Example
// next.config.js: enable critical CSS optimization
module.exports = {
experimental: {
optimizeCss: true,
},
};
// tailwind.config.js: scan only the files that actually exist
module.exports = {
content: [
"./app/**/*.{js,ts,jsx,tsx,mdx}",
"./components/**/*.{js,ts,jsx,tsx}",
],
};
// Or PurgeCSS via PostCSS:
// postcss.config.js
module.exports = {
plugins: {
"@fullhuman/postcss-purgecss": {
content: ["./app/**/*.html", "./app/**/*.tsx"],
defaultExtractor: (content) => content.match(/[\w-/:]+(?<!:)/g) || [],
},
},
};Tailwind JIT scans templates and Next.js extracts critical CSS.
Frequently asked
Coverage only captures the page state during recording. Rules used on other routes on hover on focus or after interaction will show as unused. Cross-reference with codebase grep before deleting.
Yes for most production apps. Tailwind v4's JIT mode generates only the classes you reference typically 15-50KB gzipped regardless of app size. Hand-written CSS often grows linearly with features.
If runtime CSS-in-JS is your largest non-React JS bundle yes. Linaria vanilla-extract and Pigment CSS extract styles at build time with no runtime cost and no FOUC.
Related fixes
Page speed, redirects, Core Web Vitals
Eliminate render-blocking resources slowing first paint
Page speed, redirects, Core Web Vitals
Fix FCP slow: get First Contentful Paint under 1.8s
Page speed, redirects, Core Web Vitals
Remove unused JavaScript to cut bundle size and TBT
Page speed, redirects, Core Web Vitals
Fix main thread blocked: cut Total Blocking Time and INP