Fixing Oversized Stylesheets
When the CSS Checker reports an oversized stylesheet, the file is usually big because of how it was assembled rather than because anyone wrote too much CSS. A whole UI framework imported for three components, the same partials compiled into two bundles, fonts and icons embedded as base64, and one global file served to every page are the usual culprits.
This guide is about the build: how to find out what is inside your bundle, where the bytes come from, and how to restructure imports and splitting so each page downloads a stylesheet sized for what it actually shows.
Where the bytes in a big stylesheet come from
Hand-written CSS for a typical small-business site is rarely large. The kilobytes pile up in the build pipeline, where tools pull in far more than anyone chose line by line. When you open a bloated bundle and trace its contents, you usually find a mix of these:
| Source of weight | How it gets in | Typical sign |
|---|---|---|
| Whole UI framework | One line such as @import "bootstrap"; brings in every
component | Classes for carousels, toasts and modals the site never uses |
| Duplicated partials | Variables or mixin files that also output rules, imported by several entry files | The same selectors appearing two or three times |
| Embedded assets | Fonts, SVG icons or background images inlined as data: URIs | Very
long base64 strings inside url() |
| Inline source maps | Development build settings used in production | A large comment
starting sourceMappingURL=data: at the end |
| One global bundle | Every page's styles compiled into a single file | Checkout, blog and account styles all load on the home page |
| Icon font | A full icon set loaded for a handful of icons | Hundreds of .icon-*:before rules |
| Unminified output | Minifier missing or disabled | Comments, indentation and long colour values in the served file |
Unused selectors on a single page are a related but separate problem. The unused CSS guide covers removing rules that nothing uses. This guide is about structure: what goes into each bundle, and which pages receive which bundle. How the finished file is delivered to the browser, including critical CSS and deferral, is in the render-blocking CSS guide.
Measuring what you are actually shipping
There are three sizes worth knowing for each stylesheet, and people often compare the wrong ones:
- Raw size: the file on disk after the build. This is what the browser has to parse.
- Transferred size: what travels over the network after gzip or Brotli. This is what affects download time.
- Size per page: the total of every stylesheet a given template loads. This is what a visitor actually pays for.
In DevTools, open the Network panel, filter to CSS and reload with the cache disabled. Hover the Size column, or turn on "Big request rows", to see both the transferred and the resource size. On the command line you can check the compressed size of a build output:
ls -l dist/assets/*.css
gzip -9 -c dist/assets/main.css | wc -c
brotli -c dist/assets/main.css | wc -c
To see what the bundle is made of, a CSS analyser such as Project Wallace reports the number of rules,
selectors, declarations, duplicate values and embedded data: URIs. Simple searches also
go a long way:
# How much of the file is base64 data?
grep -o 'data:[^)]*' dist/assets/main.css | wc -c
# Is the same selector compiled more than once?
grep -o '^\.btn-primary{' dist/assets/main.css | wc -l
If your build produces source maps as separate files, open the map in a text editor and look at the
sources list. It names every partial and package that contributed to the output, which quickly
shows whether an entire framework is in there.
The fix, step by step
Step 1
Measure the bundle before and after compression
Run the CSS Checker on your main templates to see which stylesheets it reports as oversized, then record the raw and compressed size of each file and the total CSS each template loads. Use the Network panel with the cache disabled, or gzip and brotli on the build output. This baseline shows whether the problem is one huge global file, too many separate files, or a compression setting missing on the server.
Step 2
Break the bundle down by source
Open the source map, or temporarily build without minification, and list which packages and partials contribute to the output. Group them into your own styles, framework code, third-party widgets and embedded assets. Search for base64 strings and repeated selectors. Most bundles have one or two dominant sources, and those are where the savings are. Write down each source with an owner, so you know which ones you can change yourself.
Step 3
Import only the framework parts you use
Replace a single all-in-one framework import with the individual pieces you need. With Bootstrap's Sass source, import the functions, variables, mixins and then only the component partials your templates use, such as the grid, buttons and forms. With Bulma or Foundation, the same idea applies. If you use a utility framework such as Tailwind, make sure its content paths cover your templates so it only generates the classes you actually write.
Step 4
Stop the same partials compiling twice
Check each entry file for partials that output real rules, such as a reset or base typography,
and make sure only one bundle includes them. Move Sass from the deprecated @import to @use
and @forward, which load each module once per compilation. Keep variables and mixins in
files that produce no CSS by themselves, so any number of entry files can use them without adding a single
byte to the output.
Step 5
Take fonts, images and source maps out of the CSS
Move base64 fonts and large background images out of the stylesheet into separate files referenced with ordinary URLs, so they can be cached and downloaded in parallel. Replace a full icon font with inline SVG or a small SVG sprite containing only the icons you use. Check your production build settings and make sure source maps are written as separate files, or not at all, rather than embedded at the end of the stylesheet.
Step 6
Split the bundle by route or template
Divide
the styles into a small shared file for the layout, header, footer and common components, plus one file
per area such as shop, blog, checkout and account. In Vite, CSS imported by lazily loaded modules is
split automatically when cssCodeSplit is on, which is the default. In webpack, use MiniCssExtractPlugin
with separate entries or split chunks. Load each area's file only on the templates that need it.
Step 7
Minify, compress and set a size budget
Minify production CSS with cssnano or Lightning CSS, and confirm the server sends it with
Brotli or gzip by checking the content-encoding response header. Give files content-hashed
names and a long cache lifetime. Then add a size budget to your build or CI, for example a check that
fails when the shared stylesheet grows past an agreed limit, so the bundle cannot quietly grow back after
the next feature or plugin.
Example: a trimmed Sass entry file
Here is the before and after for a fictional shop, Fernbrook Candles, whose single stylesheet had grown large because of a full framework import and a reset that was pulled in twice.
Before
// main.scss
@import "bootstrap"; // every component, whether used or not
@import "reset"; // also imported inside _shop.scss
@import "icons"; // full icon font, base64 embedded
@import "shop";
@import "blog";
@import "account";
After: a shared file plus per-area files
// shared.scss (loaded on every page)
// Framework foundations first, in the order the framework documents
@import "bootstrap/scss/functions";
@import "settings"; // your variable overrides, outputs nothing
@import "bootstrap/scss/variables";
@import "bootstrap/scss/variables-dark";
@import "bootstrap/scss/maps";
@import "bootstrap/scss/mixins";
@import "bootstrap/scss/utilities";
// Then only the components the templates use
@import "bootstrap/scss/root";
@import "bootstrap/scss/reboot";
@import "bootstrap/scss/containers";
@import "bootstrap/scss/grid";
@import "bootstrap/scss/buttons";
@import "layout"; // header, footer, navigation
// shop.scss (product and collection templates only)
@use "tokens"; // shared variables as a module
@use "product-card";
@use "gallery";
// blog.scss (article templates only)
@use "tokens";
@use "article";
The file list above follows the partial-import approach in Bootstrap 5's documentation, which still
uses @import for the framework's own files, so the shared file keeps that syntax while your
own area files use @use. The exact list and order change between framework versions, so
follow the documentation for the version you run. The principle is the same for any framework: pull in
the foundations, then name each component explicitly, and let each template load only its own area file.
Platform notes
WordPress
Much of the weight on WordPress comes from plugins and page builders that enqueue their own full stylesheets
on every page. You cannot restructure their builds, but you can stop their files loading where they are
not used with wp_dequeue_style() in a child theme, and choose builders that output per-page
CSS files. Block themes also offer an option to load block styles only for blocks present on the page.
The WordPress CSS guide goes further.
Shopify
Online Store 2.0 themes already split component CSS into section-level files. Weight usually comes from apps and from custom code added to one catch-all file. See the Shopify CSS guide.
React, Next.js and other frameworks
Frameworks that bundle CSS per route do much of the splitting for you, as long as styles are imported by the components that use them rather than all at once in a root layout. A global stylesheet imported at the top of the app goes to every route. CSS-in-JS libraries that generate styles at runtime add JavaScript weight as well as CSS; the React and Next.js CSS guide compares the options.
Edge cases
- Cascade order after splitting. When one file becomes several, the order they load in changes which rule wins. A rule that used to come later in the single file may now load earlier. Check pages that load more than one bundle, and consider cascade layers to make the order explicit.
- Shared components used in two areas. If the product card appears on the blog as well as the shop, put it in the shared file or in its own small file, rather than copying it into both area bundles.
- Compression already on. If the server already sends Brotli, most of the transfer saving from minification is smaller than you might expect, because repeated text compresses well. The parse cost of the raw size still matters on slower phones.
- Third-party widget CSS. Chat, review and booking widgets often bring their own large stylesheet. Load the widget only on pages that need it, or after user interaction.
Stopping the bundle from growing back
- Add a size check to your build or CI that compares each output file with a stored limit.
- Review new dependencies for their CSS as well as their JavaScript before installing them.
- Keep one owner for the shared stylesheet, so additions are a decision rather than a habit.
- Re-run the CSS Checker after each release and compare the reported sizes with your baseline.
- Keep variables and mixins in output-free files, and lint for the deprecated Sass
@import.
1. How big should a CSS file be?
There is no single correct figure, and Google does not set one. What matters is how much CSS a page has to download and parse before it can render, and how long that takes on a mid-range phone on mobile data. As a working approach, keep the shared stylesheet small enough that it arrives quickly after compression, and make sure each template loads only the area styles it uses. Measure your own pages rather than chasing a number from a blog post.
2. Is one big CSS file better than several small ones?
Neither extreme is ideal. A single file for the whole site is simple and caches well, but every page pays for styles it never uses, including checkout and account styles on the home page. Many tiny files mean more requests and more chances of one arriving late. The usual balance is a small shared file for layout and common components, plus one file per area of the site, loaded only on the templates in that area.
3. What is the difference between bundle size and unused CSS?
Bundle size is about how stylesheets are assembled and which pages receive them: framework imports, duplicated partials, embedded assets and splitting. Unused CSS is about rules that no page ever matches, which can be removed with tools such as PurgeCSS. They overlap, because a smaller bundle usually contains fewer unused rules, but they are fixed differently. Restructuring the build comes first, then removing leftover dead rules becomes easier and safer.
4. Why is my Bootstrap CSS so large?
Because a single import of the full framework includes every component it offers, from carousels and toasts to popovers and every utility class, whether your site uses them or not. Bootstrap is designed to be imported in parts. Import the functions, variables and mixins first, then only the component files you need. The documentation for your Bootstrap version lists the individual files and the order they must be imported in, which changes between major versions.
5. Does minifying CSS make much difference?
Minification removes comments, whitespace and redundant characters, and tools such as cssnano and Lightning CSS also merge duplicate rules and shorten values. The raw file gets noticeably smaller, which helps parsing. The transfer saving is often smaller than people expect when Brotli or gzip compression is already on, because compression is good at repeated text. Do both: minify at build time and make sure the server compresses CSS responses, then check the result in the Network panel.
6. Should fonts be embedded in CSS as base64?
Usually not. A base64 font is about a third larger than the original file, cannot be cached separately from the stylesheet, and makes the stylesheet itself slower to download, which delays the first paint. Serve font files as separate WOFF2 files referenced from your font-face rules, preload the one or two that appear on the first screen, and let the browser cache them. Tiny icons can sometimes be an exception, but SVG usually handles those better.
7. Will splitting CSS by route break my styles?
It can if the cascade order changes. When styles move from one file into several, rules that used to come later may now load earlier and lose to others with the same specificity. Test pages that load both the shared file and an area file, especially components used in more than one place. Cascade layers are a good way to make the intended order explicit, so it no longer depends on which file happens to load last.
The CSS Checker measures stylesheet size and flags render-blocking and unused CSS, along with validation errors, specificity issues and prefix gaps, so you know where the weight is before you restructure the build.
Open CSS Checker Fixes โ