ð tailwind-performance
Use when optimizing Tailwind CSS for production, reducing bundle size, and improving performance. Covers PurgeCSS, JIT mode, and build optimization.
Overview
Tailwind CSS includes powerful tools for optimizing your CSS for production, ensuring fast load times and minimal bundle sizes.
Key Concepts
Just-In-Time (JIT) Mode
JIT mode (default since Tailwind 3.0) generates styles on-demand as you author your templates:
Benefits:
- Lightning-fast build times
- All variants enabled by default
- Arbitrary value support
- Smaller development builds
- No separate production build needed
// tailwind.config.js (JIT is default, no config needed)
module.exports = {
content: ['./src/**/*.{html,js,jsx,ts,tsx}'],
// JIT mode is automatic
}
Content Configuration
Proper content paths are critical for performance:
module.exports = {
content: [
'./src/**/*.{js,jsx,ts,tsx}',
'./pages/**/*.{js,jsx,ts,tsx}',
'./components/**/*.{js,jsx,ts,tsx}',
'./app/**/*.{js,jsx,ts,tsx}',
// Include any files that contain Tailwind classes
'./public/index.html',
],
}
Best Practices
1. Optimize Content Paths
Be specific to avoid scanning unnecessary files:
// Good: Specific paths
module.exports = {
content: [
'./src/**/*.{js,jsx,ts,tsx}',
'./components/**/*.{js,jsx,ts,tsx}',
],
}
// Bad: Too broad
module.exports = {
content: [
'./**/*.{js,jsx,ts,tsx}', // Scans node_modules!
],
}
2. Use Safelist for Dynamic Classes
When class names are constructed dynamically, use safelist:
module.exports = {
content: ['./src/**/*.{js,jsx,ts,tsx}'],
safelist: [
'bg-red-500',
'bg-green-500',
'bg-blue-500',
// Or use patterns
{
pattern: /bg-(red|green|blue)-(400|500|600)/,
variants: ['hover', 'focus'],
},
],
}
3. Avoid String Concatenation
Don't construct class names dynamically:
// Bad: These classes won't be detected
<div className={`text-${size}`}>
<div className={`bg-${color}-500`}>
// Good: Use complete class names
<div className={size === 'large' ? 'text-lg' : 'text-sm'}>
<div className={color === 'red' ? 'bg-red-500' : 'bg-blue-500'}>
// Or use safelist for dynamic values
4. Minimize Custom CSS
Rely on utilities to reduce overall CSS size:
/* Bad: Custom CSS that duplicates utilities */
.my-button {
background-color: #3b82f6;
color: white;
padding: 0.5rem 1rem;
border-radius: 0.375rem;
}
/* Good: Use utilities or @apply */
@layer components {
.my-button {
@apply bg-blue-500 text-white px-4 py-2 rounded-md;
}
}
/* Better: Component abstraction (no custom CSS) */
5. Enable CSS Minification
Ensure your build process minifies CSS:
// postcss.config.js
module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {},
...(process.env.NODE_ENV === 'production' ? { cssnano: {} } : {})
},
}
6. Use CSS Variables Strategically
Combine Tailwind with CSS variables for theme switching without bloat:
:root {
--color-primary: 59 130 246; /* RGB */
}
[data-theme='dark'] {
--color-primary: 96 165 250;
}
// tailwind.config.js
module.exports = {
theme: {
extend: {
colors: {
primary: 'rgb(var(--color-primary) / <alpha-value>)',
},
},
},
}
Build Optimization
Vite Configuration
// vite.config.js
import { defineConfig } from 'vite'
export default defineConfig({
css: {
postcss: './postcss.config.js',
},
build: {
cssMinify: 'esbuild', // Fast CSS minification
rollupOptions: {
output: {
manualChunks: {
// Separate vendor chunks
vendor: ['react', 'react-dom'],
},
},
},
},
})
Next.js Configuration
// next.config.js
module.exports = {
experimental: {
optimizeCss: true, // Enable CSS optimization
},
// Next.js automatically optimizes Tailwind
}
Webpack Configuration
// webpack.config.js
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin')
module.exports = {
optimization: {
minimizer: [
new CssMinimizerPlugin(),
],
},
plugins: [
new MiniCssExtractPlugin({
filename: '[name].[contenthash].css',
}),
],
}
Performance Patterns
1. Code Splitting
Split CSS by route or component:
// Using dynamic imports
const HeavyComponent = lazy(() => import('./HeavyComponent'))
// Tailwind classes in HeavyComponent will be in a separate chunk
2. Critical CSS
Extract critical CSS for above-the-fold content:
<!DOCTYPE html>
<html>
<head>
<style>
/* Inline critical CSS */
.hero { /* ... */ }
.nav { /* ... */ }
</style>
<!-- Load full CSS async -->
<link rel="preload" href="/styles.css" as="style" onload="this.onload=null;this.rel='stylesheet'">
<noscript><link rel="stylesheet" href="/styles.css"></noscript>
</head>
3. Lazy Load Non-Critical Styles
// Load additional styles when needed
if (shouldLoadDarkMode) {
import('./dark-mode.css')
}
4. Font Optimization
// tailwind.config.js
module.exports = {
theme: {
extend: {
fontFamily: {
sans: [
'Inter var',
'system-ui',
'-apple-system',
'BlinkMacSystemFont',
'"Segoe UI"',
'Roboto',
'sans-serif',
],
},
},
},
}
/* Use font-display for better loading */
@font-face {
font-family: 'Inter var';
font-style: normal;
font-weight: 100 900;
font-display: swap; /* Prevent invisible text */
src: url('/fonts/inter-var.woff2') format('woff2');
}
Monitoring Performance
Bundle Size Analysis
# Analyze CSS bundle size
npx tailwindcss -i ./src/input.css -o ./dist/output.css --minify
# Check file size
ls -lh dist/output.css
# Detailed analysis with webpack-bundle-analyzer
npm install --save-dev webpack-bundle-analyzer
Lighthouse Metrics
Target metrics:
- First Contentful Paint (FCP): < 1.8s
- Largest Contentful Paint (LCP): < 2.5s
- Cumulative Layout Shift (CLS): < 0.1
- CSS Bundle Size: < 50KB (gzipped)
Performance Checklist
â
Content paths are specific and optimized
â
JIT mode is enabled (default in Tailwind 3+)
â
CSS is minified in production
â
Unused styles are purged
â
Dynamic classes use safelist
â
Critical CSS is inlined
â
Fonts use font-display: swap
â
CSS is code-split by route/chunk
â
Gzip/Brotli compression enabled
â
CSS file has content hash for caching
Examples
Production Build Script
// package.json
{
"scripts": {
"dev": "vite",
"build": "vite build",
"build:css": "tailwindcss -i ./src/input.css -o ./dist/output.css --minify",
"analyze": "npm run build && webpack-bundle-analyzer dist/stats.json"
}
}
Optimized Configuration
// tailwind.config.js
module.exports = {
content: {
files: [
'./src/**/*.{js,jsx,ts,tsx}',
'./components/**/*.{js,jsx,ts,tsx}',
],
// Only in dev: watch for changes
relative: process.env.NODE_ENV === 'development',
},
theme: {
extend: {
// Only extend what you need
colors: {
primary: 'rgb(var(--color-primary) / <alpha-value>)',
},
},
},
plugins: [
// Only include plugins you use
require('@tailwindcss/forms'),
],
// Disable unused variants
corePlugins: {
// Disable unused features
preflight: true,
// Only enable what you need
},
}
CDN vs Bundle Comparison
<!-- Bad: CDN (3.5MB+, not optimized) -->
<link href="https://cdn.jsdelivr.net/npm/tailwindcss@3/dist/tailwind.min.css" rel="stylesheet">
<!-- Good: Bundled & optimized (typically 5-20KB gzipped) -->
<link href="/dist/styles.css" rel="stylesheet">
Common Pitfalls
â Using CDN in Production
<!-- Never do this in production -->
<script src="https://cdn.tailwindcss.com"></script>
The CDN build is 3.5MB+ and includes all utilities. Always use a build process.
â Overly Broad Content Paths
// Bad: Scans everything including node_modules
content: ['./**/*.html']
// Good: Specific to your source files
content: ['./src/**/*.{html,js,jsx,ts,tsx}']
â Not Using Safelist for Dynamic Classes
// Bad: Class won't be included in build
const colors = ['red', 'blue', 'green']
<div className={`bg-${colors[index]}-500`} />
// Good: Use safelist or conditional classes
â Importing Full Tailwind in Components
// Bad: Imports all of Tailwind
import 'tailwindcss/tailwind.css'
// Good: Import only what you built
import './styles.css'
Anti-Patterns
â Don't Disable Purge in Production
// Bad: Never do this
module.exports = {
content: [], // Empty = no purging!
}
â Don't Use @apply Excessively
/* Bad: Defeating the purpose of utilities */
.btn { @apply px-4 py-2 bg-blue-500 text-white rounded; }
.card { @apply p-6 bg-white shadow-lg rounded-lg; }
.header { @apply flex items-center justify-between p-4; }
/* ...hundreds of components */
/* This negates Tailwind's optimization benefits */
â Don't Ignore Build Warnings
# Pay attention to warnings like:
# "The content option in your Tailwind CSS configuration is missing or empty"
# "No utility classes were detected in your source files"
Related Skills
- tailwind-configuration: Customizing Tailwind config and theme
- tailwind-utility-classes: Using Tailwind's utility classes effectively
- tailwind-responsive-design: Building responsive designs efficiently