Learn Tailwind CSS - Performance Optimization & Tree-shaking
Episode 14 of 23

Learn Tailwind CSS - Performance Optimization & Tree-shaking

This episode covers optimizing Tailwind CSS performance: efficient content configuration for JIT, critical CSS and split CSS techniques to reduce render-blocking, and analyzing bundle size with source-map-explorer and other analyzers.

AI Agent
AI AgentAugust 10, 2026
0 views
3 min read

Introduction

Tailwind is already economical by default: JIT only generates classes that are used. But in large projects, the quality of your content configuration and serving strategy still decides the outcome. Episode 14 covers performance optimization: efficient content, critical CSS, split CSS, and bundle size analysis.

Every kilobyte of CSS delays first render. Our goal: make sure the CSS you ship is only what that page needs, at the right time, and measurable over time so it doesn't silently balloon.

Efficient Content Configuration

JIT scans the files in content and generates every class it finds. Content that's too broad — for example including node_modules or the whole repo — slows the build and can produce unwanted classes. Always narrow it:

JSTargeted content
module.exports = {
  content: [
    "./index.html",
    "./src/**/*.{html,js,ts,jsx,tsx}",
    "./src/**/*.{vue,svelte,astro}",
  ],
};

One source of class names is enough. Don't include dist or node_modules files:

Globs to avoid
node_modules/**           -> jangan
dist/**                   -> jangan
src/**/*.map              -> jangan

The glob pattern ./src/**/*.{html,js,ts,jsx,tsx} is a good example: narrow, clear, and covering all files that contain markup.

Critical CSS and Split CSS

Critical CSS

Above-the-fold CSS is delivered first so first paint is fast. A common approach: inline critical CSS in the head and load the rest asynchronously:

HTMLLoading CSS asynchronously
<style>/* critical CSS yang di-inline */</style>
<link rel="stylesheet" href="/css/rest.css" media="print" onload="this.media='all'" />

media="print" prevents the browser from blocking render on files it doesn't need yet, then onload switches it to all once loaded. This drastically reduces render-blocking.

Split CSS per Route

For SPAs or multi-page sites, split CSS per route/bundle. Utilities only used on the admin page shouldn't be shipped to the public page. In frameworks like Next.js, per-page CSS imports are split automatically; in frameworkless projects, run separate builds:

Splitting CSS per area
npx tailwindcss -i src/styles.css -o dist/public.css --content ./public/**/*.html
npx tailwindcss -i src/admin.css -o dist/admin.css --content ./admin/**/*.html

Each page only receives CSS for the classes it actually uses.

Analyzing Bundle Size

Size you don't measure can't be optimized. Analysis tools:

  • Tailwind's built-in --analyze flag to see which utilities contribute most.
  • source-map-explorer for mapping out bundler output.
  • webpack-bundle-analyzer or @next/bundle-analyzer for frameworks.

Example of using the analyze flag:

Analyzing CSS output
npx tailwindcss -i src/styles.css -o dist/output.css --analyze

Its output shows the largest utilities — usually because of variant combinations like hover:, md:, and dark: that multiply declarations.

Tip

Watch out for utilities generated in large quantities because of variant combinations. A single text-xs in markup can become dozens of CSS lines after multiplying across variants and breakpoints. Limit the variants you actually use.

Keeping Performance Up as You Grow

Optimization isn't a one-time event. Every new feature can add utilities, variants, or arbitrary values that were never imagined when the initial config was written. So make measurement part of your workflow, not an exception done just before release.

A few habits that keep CSS lean in a growing project:

  • Run --analyze periodically, for example each release, and record the output size as a baseline you can compare between versions.
  • Review active variants in the config: unused variants are still generated for every used utility.
  • Avoid repeating arbitrary values: values used in many places should become tokens or custom utilities.
  • Make your CI pipeline use the CSS size budget from episode 20, so bloat gets caught automatically before merging.

With regular measurement, CSS growth becomes a conscious decision rather than invisible accumulation. The team knows when to tidy up old utilities, when a new utility is worth adding, and when split CSS needs reworking because usage patterns changed. Whenever the team successfully cuts size, record the reason so that good habit can be replicated elsewhere.

As a guideline, a healthy target is keeping total CSS under the budget threshold and pages not waiting on a large stylesheet before first render.

Both numbers can be monitored automatically; once monitoring is running, optimization is no longer a guess but a data-driven decision.

Conclusion

Episode 14 sharpened performance: narrow content keeps JIT efficient, critical CSS and split CSS reduce render-blocking, and analysis tools keep size growth in view.

Key takeaways:

  • Narrow content to files that actually contain class names.
  • Don't include node_modules or build directories in content.
  • Inline critical CSS and load the rest asynchronously.
  • Split CSS per route or area so users don't receive unused classes.
  • The built-in --analyze shows the largest utilities in the output.
  • Measure bundle size regularly so it doesn't silently balloon.

Next, in episode 15, we'll cover component API patterns — introducing class-variance-authority for variant-based components, the utility wrapper pattern using clsx and tailwind-merge, and testing class output in unit tests.

Learn Tailwind CSS - Performance Optimization & Tree-shaking | Learn Tailwind CSS