Unused CSS
What is this issue?
Unused CSS is style rules a page downloads but never applies to anything on it -- usually because one site-wide stylesheet carries the rules for every page, or a CSS framework is included whole.
This suggestion is raised when Lighthouse finds 10 KiB or more of unused CSS on a page.
Why it matters
- Stylesheets block rendering. The browser will not paint until every stylesheet in
the
<head>has downloaded and been parsed, unused rules included. - It is usually a small cost, which is why this is a suggestion: some unused CSS is unavoidable with a shared stylesheet that is cached across pages.
- Large amounts point at a framework included whole where a purge step would remove most of it.
How to fix it
Purge unused rules at build time. Tailwind does this by default; for other frameworks use a tool such as PurgeCSS.
Split CSS by page or component so each page loads its own rules, and inline the few the first screen needs.
Drop framework modules you do not use -- import the grid and buttons, not the whole of Bootstrap.
Check the Coverage panel in Chrome DevTools to see which rules each page used.
Examples
1. A whole CSS framework
Fails -- 142 KiB unused: the full Bootstrap stylesheet on a page that uses its grid.
Passes -- a custom build with only the grid and utilities: 9 KiB unused.
2. Just under the floor
Passes -- 9.99 KiB of unused rules in a shared stylesheet. Lighthouse would not list it, and neither does PixyScan.
How PixyScan detects this
Chooses which pages to measure. After the crawl, PixyScan measures the homepage plus the pages your own site links to most, up to a per-scan budget (ten pages by default, at most fifty). Measuring every page would take hours and exhaust the PageSpeed Insights quota of the API key it runs under.
Asks the PageSpeed Insights API about each of them, on mobile. This needs the Hobby plan or above and a PageSpeed Insights API key on the site or its workspace.
Reads the Lighthouse audit
unused-css-rules, which uses Chrome's CSS coverage during the lab load to list each stylesheet with at least 10 KiB of rules that matched nothing.Raises the suggestion when the total unused bytes reach 10 KiB -- Lighthouse's own per-stylesheet floor, so in practice when Lighthouse lists any sheet.
Raises nothing on a page it did not measure. A page outside the sample, and a page whose measurement failed, are both reported as
Not measured. Neither is treated as passing, and neither can fail this check.
What we store
Storage Level
Page Level
Database Table / Prisma Model
PagePerformance (page_performance), with the finding itself on
audit_issues.details.
Fields Used
| Field | Type | Description |
|---|---|---|
| strategy | String | mobile or desktop. Every number in the row belongs to one form factor. |
| fetchedAt | DateTime | When PageSpeed answered. "Measured three weeks ago" is not "not measured". |
| errorReason | String? | Why a sampled page still has no numbers. Null when the call succeeded. |
| audits | Json? | A compact summary of the Lighthouse audits the opportunity checks read: per audit its source (the Lighthouse audit id it was read from), score, numericValue, displayValue, itemCount, totals over every row (wastedBytes, wastedMs, blockingTime, mainThreadTime) and the five worst rows. Keyed by the classic Lighthouse audit id. Null when the response carried no audits. |
On the finding (audit_issues.details): message, plus audits (unused-css-rules), wastedBytes, thresholdBytes (10240), fileCount and items (the five stylesheets with the most unused bytes).
Detection Dependencies
- PageSpeed Insights API v5 (
lighthouseResult.audits), mobile strategy - The PageSpeed plan feature (Hobby and above) and a PageSpeed Insights API key
- The crawl's internal link graph, which decides which pages are sampled
Note
A page with no row was never measured. The pass samples the homepage plus the
most-linked pages within a per-scan budget (default 10, at most 50), so most pages of a
site have no page_performance row at all. That is the normal state, it is drawn as
Not measured, and this check cannot be raised against it.
A row whose metric columns are all null with an errorReason set is a different state
again: the page WAS sampled and the measurement failed. It is still never reported as
passing.