Tailwind CSS Bundle Analysis: Tools to Measure Size
AI generated
</>
tw
Tailwind CSS · Bundle Analysis · Performance · CI
Tailwind CSS Bundle Analysis
tools and workflows to make CSS size measurable

Without bundle analysis, the size of generated Tailwind CSS stays a black box that only becomes noticeable once users complain about slow load times. With the right tools you can measure CSS size, visualize utility usage, and automatically guard against unnoticed growth via CI budgets.

18 min read PostCSS Reports · Source Map Explorer · CI Budgets Tailwind CSS v4

1. Why bundle analysis is more than a file size

Surface level bundle analysis often stops at checking the file size of the generated Tailwind CSS with ls -la. That gives you a single number but no explanation for where that number comes from or whether it represents a problem. Meaningful bundle analysis instead answers questions like: which utility groups contribute the most to the total size? Are there unused selectors that made it into the final CSS despite content scanning? How does the size change across multiple releases?

The difference becomes especially clear as a project grows over months: without systematic bundle analysis it stays unclear whether a new safelist rule, an additional utility plugin, or simply more components are responsible for a larger stylesheet. With the right tools, every size change can be traced back to a concrete cause instead of guessing from scratch with every increase.

2. Base metrics: raw, gzip, and brotli

The first step of any bundle analysis is choosing the right metric. Raw file size shows how much text the browser has to parse, while the actual transfer size over the network depends on the compression used. Gzip typically compresses CSS to ten to fifteen percent of the original size thanks to its high redundancy of class names and selectors, brotli often achieves slightly better results at similar compression time.

For a reliable bundle analysis, all three values should be captured in parallel: raw for parse time estimates, gzip as a practical baseline for most CDN configurations, and brotli as the target value for modern hosting environments. A CSS bundle that grows twenty percent uncompressed might still only grow five percent after compression if the increase consists of highly repetitive patterns that compress well.


# Basic Tailwind bundle analysis: raw, gzip, and brotli size in one command
FILE="dist/styles.css"

RAW=$(wc -c < "$FILE")
GZIP=$(gzip -9 -c "$FILE" | wc -c)
BROTLI=$(brotli -q 11 -c "$FILE" | wc -c)

echo "Raw:    $((RAW / 1024)) KB"
echo "Gzip:   $((GZIP / 1024)) KB"
echo "Brotli: $((BROTLI / 1024)) KB"

3. PostCSS reports on utility distribution

A PostCSS plugin like postcss-reporter combined with a custom analysis plugin delivers deeper insights for bundle analysis than a raw file size ever could. Such a plugin can count every generated CSS rule during processing and categorize it by utility group like colors, spacing, typography, or layout. This quickly reveals whether, for example, the color palette with hundreds of generated opacity variants is responsible for a disproportionately large share of the bundle.

For bundle analysis in existing projects, a simple Node script that traverses the PostCSS AST of the generated stylesheet and prints the number of rules and their cumulative character length per utility prefix is a good starting point. Such a report can be written in a few minutes and delivers project specific insights that generic analysis tools often miss because they do not know Tailwind's naming conventions.


// scripts/analyze-css.mjs — group generated rules by utility prefix
import postcss from "postcss";
import fs from "node:fs";

const css = fs.readFileSync("dist/styles.css", "utf8");
const root = postcss.parse(css);
const groups = new Map();

root.walkRules((rule) => {
  // crude but effective: use the first class segment as the group key
  const match = rule.selector.match(/\.([a-z0-9]+)/i);
  const group = match ? match[1].split("-")[0] : "other";
  const size = rule.toString().length;
  groups.set(group, (groups.get(group) || 0) + size);
});

[...groups.entries()]
  .sort((a, b) => b[1] - a[1])
  .slice(0, 10)
  .forEach(([group, size]) => console.log(`${group}: ${(size / 1024).toFixed(1)} KB`));

4. Source map explorer for CSS bundles

Tools like source-map-explorer, originally built for JavaScript bundles, can also be used for the bundle analysis of CSS as long as the build process generates CSS source maps. The advantage over pure rule counting: source map explorer shows which original source file each CSS rule came from, not just which utility group it belongs to. This is especially helpful in monorepos where multiple packages contribute to the final bundle.

For meaningful bundle analysis with source maps, the PostCSS pipeline needs to explicitly configure map: { inline: false } so a separate .css.map file gets created that the analysis tools can read. In production builds, this source map generation should stay disabled to avoid shipping unnecessary files and potentially sensitive path information. It is intended purely for local or CI internal analysis runs.


# Generate a CSS source map for analysis purposes (dev/CI only, not production)
npx tailwindcss -i ./src/input.css -o ./dist/styles.css --map

# Inspect the resulting bundle with source-map-explorer
npx source-map-explorer dist/styles.css dist/styles.css.map \
  --html dist/bundle-report.html

5. cssnano reports and finding unused rules

The minifier cssnano, running in most Tailwind production pipelines, offers its own statistics on performed optimizations via its --verbose mode. For bundle analysis, it is particularly interesting how many duplicate selectors cssnano merges and how many bytes get saved by removing redundant declarations. A high share of duplicates often indicates that multiple components repeatedly generate the same utility combinations individually instead of using a shared component class.

In addition to minification statistics, a comparison between the theoretical CSS size that would result from full usage of every utility class referenced in the codebase, and the actually generated size, is helpful. Larger deviations point to content scanning gaps where classes exist in the code but are not detected due to faulty content configuration. This comparison is an important part of a complete bundle analysis because it can explain both oversized and unexpectedly small bundles.

6. Visualization: treemaps for selector groups

Numbers in a console output are useful for a quick bundle analysis, but a visual treemap makes size relationships graspable at a glance. Tools like webpack-bundle-analyzer can be repurposed with a CSS to JSON adapter to display utility groups as proportionally sized rectangles. A color group that makes up twenty percent of the bundle stands out immediately in a treemap, while the same information in a numeric list is easily overlooked.

For teams that regularly post bundle analysis reports in pull requests, it is worth automating this visualization via a GitHub Action that attaches a fresh treemap image as a comment on every build. This way, reviewers immediately see whether a change introduces a new, unexpectedly large utility group without having to trigger the analysis manually.


// scripts/export-treemap-data.mjs — export grouped sizes as treemap-ready JSON
import fs from "node:fs";

const groups = { colors: 18400, spacing: 9200, layout: 6100, typography: 3400 };

const treemapData = {
  name: "tailwind-bundle",
  children: Object.entries(groups).map(([name, size]) => ({ name, value: size })),
};

fs.writeFileSync("dist/bundle-treemap.json", JSON.stringify(treemapData, null, 2));

7. CI budgets: catching regressions automatically

The most valuable form of bundle analysis is not a one time investigation but a continuously running CI budget that checks every pull request pipeline against a defined size threshold. If the generated CSS exceeds this threshold, the pipeline fails and alerts the team to the regression before it reaches production. Without such a budget, gradual size increases across many small commits often go unnoticed until they add up to a noticeable performance problem.

A realistic CI budget for bundle analysis should not only check total size but also the percentage increase compared to the last merge into the main branch. A jump from fifty to fifty five kilobytes gzip is usually unproblematic, while the same absolute increase on an originally ten kilobyte bundle is a warning sign. Percentage thresholds catch such relative regressions more reliably than rigid absolute limits.


# .github/workflows/css-budget.yml — fail the pipeline on CSS size regressions
name: css-budget
on: [pull_request]
jobs:
  bundle-analysis:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with: { node-version: 20 }
      - run: npm ci
      - run: npx tailwindcss -i ./src/input.css -o ./dist/styles.css --minify

      - name: Check gzip size against budget
        run: |
          GZIP_KB=$(gzip -9 -c dist/styles.css | wc -c | awk '{print int($1/1024)}')
          BUDGET_KB=60
          echo "Current gzip size: ${GZIP_KB} KB (budget: ${BUDGET_KB} KB)"
          if [ "$GZIP_KB" -gt "$BUDGET_KB" ]; then
            echo "::error::CSS bundle exceeds budget of ${BUDGET_KB} KB"
            exit 1
          fi

8. Lighthouse CI and real load time metrics

Pure file size metrics are a good starting point for bundle analysis but say nothing about the actual impact on user experience. Lighthouse CI closes this gap by simulating real page loads in a controlled environment and capturing metrics like First Contentful Paint and Largest Contentful Paint, which are directly influenced by CSS load time. A growing CSS bundle shows up here not as an abstract kilobyte number but as a concrete millisecond delay in rendering.

Combining static bundle analysis with Lighthouse CI metrics in the same CI pipeline provides a more complete picture: the static analysis explains where a size change comes from, while Lighthouse shows whether that change is actually noticeable to users. Some size increases, for example from additional but rarely used utility classes, barely affect load time, while others, for example from bloated critical above the fold styles, cause clearly visible delays.

9. Analysis tools compared

Depending on project size and team workflow, different tools suit bundle analysis differently well. The following overview arranges the presented approaches by use case.

Tool Strength Use case
gzip/brotli script Fast, no dependencies Daily size check, CI budget
PostCSS analysis script Tailwind specific utility grouping Root cause research on size growth
source-map-explorer Shows origin file per rule Monorepos with multiple packages
Lighthouse CI Real load time impact Validating user experience

A mature workflow combines several of these tools: a fast gzip script for daily CI runs, the PostCSS analysis for deeper investigations when needed, and Lighthouse CI at regular intervals to keep an eye on actual user impact. This combination delivers a complete bundle analysis without running the most expensive analysis form on every single commit.

Mironsoft

CSS performance, bundle monitoring, and CI budget setup

Control over the size of your Tailwind bundle?

We set up bundle analysis reports, PostCSS statistics, and CI budgets so CSS growth becomes visible immediately and never reaches production unnoticed.

Analysis setup

Configure PostCSS reports and source map explorer for your project

CI budget

Integrate absolute and percentage thresholds into your pipeline

Lighthouse integration

Capture real load time metrics alongside static size values

10. Summary

A complete bundle analysis for Tailwind CSS starts with base metrics, raw, gzip, and brotli, moves through PostCSS based utility grouping and source map explorer, and reaches automated CI budgets that catch regressions early. Anyone looking only at a single file size misses the explanation for why a bundle grows and whether that growth is actually noticeable to users.

Lighthouse CI complements static size measurements with real load time metrics and makes clear that not every kilobyte increase carries equal weight. A mature workflow combines fast, daily checks with deeper analysis when needed, and ensures that bundle analysis becomes part of the normal development process instead of remaining a rare, manual emergency measure.

Tailwind CSS Bundle Analysis — Key Takeaways

Three metrics

Capture raw, gzip, and brotli in parallel to cover parse time and network size.

Utility grouping

PostCSS scripts show which class groups contribute the most to size.

CI budgets

Absolute and percentage thresholds catch regressions automatically.

Real metrics

Lighthouse CI shows whether CSS growth actually affects load time noticeably.

11. FAQ: Tailwind CSS Bundle Analysis

1What is bundle analysis?
Systematic examination of size, utility distribution, and growth of generated CSS over time.
2Is raw file size enough?
No, capture raw, gzip, and brotli in parallel for a complete picture.
3Find the biggest utility group?
A PostCSS script groups generated rules by utility prefix and shows the distribution.
4What is source-map-explorer for?
Shows the origin file of each CSS rule, helpful in monorepos with multiple packages.
5Source maps in production?
No, only for local or CI internal analysis, disable in production builds.
6Reasonable CI budget?
Absolute upper limit plus percentage increase compared to the last merge.
7Why not just a CI budget?
A budget only checks size, Lighthouse CI shows actual user impact.
8Detect content scanning gaps?
Deviations between expected and generated CSS amount point to configuration errors.
9What does cssnano verbose show?
Performed optimizations like merged duplicates, hinting at repeated utility combinations.
10How often should it run?
Fast size check on every PR, deeper analysis at regular intervals.