Tailwind CSS ships only the classes you use — in theory. In practice, misconfigured projects ship far more CSS than needed, and common patterns like arbitrary values and @apply can silently bloat your stylesheet. Here's how to keep your Tailwind bundle under 10 KB.
How Tailwind's JIT works
Tailwind v3+ uses a Just-In-Time compiler that scans your source files for class names and generates only the CSS those classes need. The output is a pure, minimal stylesheet — no unused classes, no reset bloat beyond what you configure.
The JIT engine scans files that match patterns in your tailwind.config.js content array. If a class name doesn't appear as a complete string in a scanned file, its CSS isn't generated.
Audit your CSS bundle size
After a production build (npm run build), check the CSS output in .next/static/css/. A well-configured Tailwind app should produce:
- Under 10 KB gzipped for apps with moderate styling complexity
- 20–40 KB for design-system-heavy apps with many variants
- Over 100 KB is a red flag — something is wrong in the configuration
Content glob misconfiguration
The most common cause of oversized Tailwind bundles is an overly broad content glob:
// BAD: scans everything, including node_modules
content: ["./**/*.{js,ts,jsx,tsx}"]
// GOOD: scans only your source
content: ["./src/**/*.{js,ts,jsx,tsx}", "./app/**/*.{js,ts,jsx,tsx}"]
If your glob accidentally scans node_modules or test fixtures that contain Tailwind class names, every class those files reference gets generated — even if none of those files ship to production.
Arbitrary values: necessary but expensive
Tailwind's arbitrary value syntax (w-[337px], bg-[#1a2b3c]) generates a unique CSS rule for each value. Each one adds ~40 bytes to the stylesheet. Fifty arbitrary values add ~2 KB. That's fine. But projects with hundreds of arbitrary values from design-system one-offs should consolidate them into custom tokens in tailwind.config.js instead:
theme: { extend: { spacing: { '18': '4.5rem', '88': '22rem' } } }
@apply abuse
@apply expands Tailwind utilities into regular CSS at build time. It's useful for component classes in global CSS. It becomes a problem when used inside component files where Tailwind's JIT already handles the job:
- Good use of @apply: defining base styles for HTML elements in your global CSS (
h1 { @apply text-3xl font-bold }) - Bad use of @apply: creating component-level utility classes that you then apply to JSX elements — the component file already has access to Tailwind classes directly.
Safelist sparingly
The safelist option forces specific classes to be generated even if they don't appear in content files. This is necessary for dynamically constructed class names:
safelist: ["bg-red-500", "bg-green-500", "bg-yellow-500"]
Avoid using regex patterns in safelist (safelist: [{ pattern: /bg-.+/ }]) — this generates every variant of matching classes, often adding 20–100 KB to the bundle.
Plugin impact
Official Tailwind plugins (Typography, Forms, Aspect Ratio) add CSS. Measure the impact of each plugin with and without it:
@tailwindcss/typography: +8–15 KB (worth it for blog content)@tailwindcss/forms: +2–4 KB- Third-party plugins: measure individually, some add 50+ KB
Purge verification
After optimization, verify that the CSS bundle size is what you expect:
find .next/static/css -name '*.css' | xargs wc -c
Compare the number before and after each optimization step. Track it in CI to prevent regressions.