Master Just-In-Time (JIT) compilation and performance optimization in Tailwind CSS. Learn how JIT mode works, optimize builds for production, and implement best practices for fast, efficient stylesheets.
Just-In-Time (JIT) mode revolutionized Tailwind CSS by generating styles on-demand as you write them, rather than generating everything upfront. This results in lightning-fast build times during development, significantly smaller CSS files in production, and the ability to use any arbitrary value without configuration.
Since Tailwind CSS v3.0, JIT mode is the default and only mode. Understanding how it works and how to optimize it is crucial for building performant applications with minimal CSS overhead.
In this lesson, you'll master JIT compilation, learn production optimization techniques, implement effective purging strategies, and apply best practices to keep your stylesheets fast and lean.
Development Speed: Instant compilation of only the classes you use
Production Performance: Tiny CSS bundles (often < 10KB gzipped)
Unlimited Flexibility: Use arbitrary values like w-[473px] without configuration
Consistent Experience: Same CSS in development and production
Let's dive into JIT mode and performance optimization to build blazing-fast applications.
Just-In-Time (JIT) compilation generates CSS on-demand based on the classes detected in your source files, rather than generating all possible combinations upfront.
Traditional Mode (Pre-v3.0):
JIT Mode (v3.0+):
JIT is enabled by default in Tailwind v3.0+. No configuration needed!
1// tailwind.config.js (v3.0+)
2/** @type {import('tailwindcss').Config} */
3module.exports = {
4 content: [
5 './src/**/*.{js,jsx,ts,tsx}',
6 './public/index.html',
7 ],
8 theme: {
9 extend: {},
10 },
11 plugins: [],
12}For Tailwind v2.x (Legacy):
1// tailwind.config.js (v2.x)
2module.exports = {
3 mode: 'jit', // Enable JIT mode
4 purge: [
5 './src/**/*.{js,jsx,ts,tsx}',
6 './public/index.html',
7 ],
8 // ... rest of config
9}1. Lightning-Fast Build Times
Development builds are instant because Tailwind only generates the CSS you're actually using.
1# Traditional mode
2Build time: 3-8 seconds (initial), 1-3s (rebuilds)
3
4# JIT mode
5Build time: < 500ms (initial), < 100ms (rebuilds)2. Smaller Development CSS
Your development CSS is the same size as production, making browser DevTools faster.
1Traditional mode (dev): ~3-4 MB uncompressed CSS
2JIT mode (dev): ~10-50 KB uncompressed CSS3. Arbitrary Values
Use any value without configuration:
1<!-- Width with arbitrary value -->
2<div class="w-[473px]">Exactly 473px wide</div>
3
4<!-- Custom color with arbitrary value -->
5<div class="bg-[#1da1f2]">Twitter blue background</div>
6
7<!-- Custom spacing -->
8<div class="p-[17px]">17px padding</div>
9
10<!-- Complex values -->
11<div class="grid-cols-[200px_minmax(900px,_1fr)_100px]">
12 Custom grid template
13</div>4. All Variants Enabled
Every variant works with every utility automatically:
1<!-- All variants work without configuration -->
2<div class="hover:scale-110 focus:rotate-3 active:brightness-90 dark:bg-gray-800 md:flex lg:grid-cols-3">
3 All variants available
4</div>5. Stackable Variants
Combine multiple variants:
1<!-- Combine hover + dark mode -->
2<div class="dark:hover:bg-blue-500">
3 Hover background in dark mode
4</div>
5
6<!-- Combine responsive + state -->
7<div class="md:hover:scale-105 lg:focus:ring-4">
8 Responsive state variants
9</div>
10
11<!-- Group modifiers with variants -->
12<div class="group">
13 <div class="group-hover:dark:text-blue-400">
14 Complex variant stacking
15 </div>
16</div>JIT scans your source files using static analysis - it looks for complete class name strings.
✅ Works (Static Strings):
1// Complete class strings
2<div className="bg-blue-500 text-white">Works</div>
3
4// Template literals with static parts
5<div className={`bg-${color}-500`}>Works if 'color' values are predictable</div>
6
7// Conditional classes
8<div className={isActive ? 'bg-blue-500' : 'bg-gray-500'}>Works</div>❌ Doesn't Work (Dynamic Strings):
1// Dynamic construction
2<div className={`text-${size}`}>Might not work</div>
3
4// Computed values
5const spacing = computeSpacing()
6<div className={`p-${spacing}`}>Might not work</div>💡 Best Practice: Use Complete Class Names
1// ❌ Bad - dynamic construction
2<div className={[text-${color}-500]}>
3
4// ✅ Good - complete strings
5<div className={color === 'blue' ? 'text-blue-500' : 'text-red-500'}>
6
7// ✅ Good - safelist in config if needed
8// tailwind.config.js
9module.exports = {
10 safelist: [
11 'text-blue-500',
12 'text-red-500',
13 'text-green-500',
14 ],
15}Development Mode:
Production Mode:
1// package.json scripts
2{
3 "scripts": {
4 "dev": "tailwindcss -i ./src/input.css -o ./dist/output.css --watch",
5 "build": "NODE_ENV=production tailwindcss -i ./src/input.css -o ./dist/output.css --minify"
6 }
7}The content configuration tells Tailwind where to look for class names to generate CSS. Proper configuration is critical for JIT mode.
1// tailwind.config.js
2module.exports = {
3 content: [
4 './src/**/*.{js,jsx,ts,tsx}',
5 './public/index.html',
6 ],
7 // ... rest of config
8}Use glob patterns to match files:
1module.exports = {
2 content: [
3 // Match all JS/JSX/TS/TSX files in src directory and subdirectories
4 './src/**/*.{js,jsx,ts,tsx}',
5
6 // Match specific directories
7 './pages/**/*.{js,jsx,ts,tsx}',
8 './components/**/*.{js,jsx,ts,tsx}',
9
10 // Match HTML files
11 './public/*.html',
12 './*.html',
13
14 // Match Vue files
15 './src/**/*.vue',
16
17 // Match Svelte files
18 './src/**/*.svelte',
19
20 // Match PHP files (WordPress, Laravel)
21 './templates/**/*.php',
22 './**/*.blade.php',
23 ],
24}Next.js:
1module.exports = {
2 content: [
3 './pages/**/*.{js,ts,jsx,tsx}',
4 './components/**/*.{js,ts,jsx,tsx}',
5 './app/**/*.{js,ts,jsx,tsx}', // App Router
6 './src/**/*.{js,ts,jsx,tsx}',
7 ],
8}Create React App:
1module.exports = {
2 content: [
3 './src/**/*.{js,jsx,ts,tsx}',
4 './public/index.html',
5 ],
6}Vue.js:
1module.exports = {
2 content: [
3 './index.html',
4 './src/**/*.{vue,js,ts,jsx,tsx}',
5 ],
6}Svelte:
1module.exports = {
2 content: [
3 './src/**/*.{html,js,svelte,ts}',
4 ],
5}Laravel:
1module.exports = {
2 content: [
3 './storage/framework/views/*.php',
4 './resources/**/*.blade.php',
5 './resources/**/*.js',
6 './resources/**/*.vue',
7 ],
8}Scan raw content strings directly:
1module.exports = {
2 content: [
3 './src/**/*.{js,jsx,ts,tsx}',
4 {
5 raw: '<div class="bg-blue-500 text-white p-4">Raw content</div>',
6 },
7 ],
8}Transform content before scanning (useful for obscure syntaxes):
1module.exports = {
2 content: {
3 files: [
4 './src/**/*.{js,jsx,ts,tsx}',
5 ],
6 transform: {
7 tsx: (content) => {
8 // Transform TypeScript JSX content
9 return content.replace(/oldPattern/g, 'newPattern')
10 },
11 },
12 },
13}Customize how class names are extracted:
1module.exports = {
2 content: {
3 files: [
4 './src/**/*.{js,jsx,ts,tsx}',
5 ],
6 extract: {
7 jsx: (content) => {
8 // Custom extraction logic
9 return content.match(/className="([^"]*)"/g)?.map(m => m.replace(/className="/g, '').replace(/"/g, ''))
10 },
11 },
12 },
13}1. Be Specific
1// ❌ Too broad - scans unnecessary files
2content: ['./**/*.{js,jsx,ts,tsx}']
3
4// ✅ Specific - only scans source files
5content: ['./src/**/*.{js,jsx,ts,tsx}']2. Exclude Build Artifacts
1// ❌ Includes build output
2content: ['./**/*.js']
3
4// ✅ Excludes build directories
5content: [
6 './src/**/*.js',
7 '!./dist/**',
8 '!./build/**',
9 '!./node_modules/**',
10]3. Include All Relevant Files
1// ✅ Comprehensive - includes all files with classes
2content: [
3 './src/**/*.{js,jsx,ts,tsx}',
4 './components/**/*.{js,jsx,ts,tsx}',
5 './public/index.html',
6 './src/**/*.css', // If you use @apply
7]4. Use Safelist for Dynamic Classes
1module.exports = {
2 content: ['./src/**/*.{js,jsx,ts,tsx}'],
3 safelist: [
4 // Safelist individual classes
5 'bg-red-500',
6 'bg-green-500',
7 'bg-blue-500',
8
9 // Safelist with patterns
10 {
11 pattern: /bg-(red|green|blue)-(100|500|900)/,
12 },
13
14 // Safelist with variants
15 {
16 pattern: /bg-(red|green|blue)-500/,
17 variants: ['hover', 'focus'],
18 },
19 ],
20}Watch Mode Performance:
1// Use specific paths for faster scanning
2content: [
3 './src/components/**/*.{js,jsx}', // Specific
4 './src/pages/**/*.{js,jsx}', // Instead of
5 // './src/**/*.{js,jsx}' // Too broad
6]Monorepo Configuration:
1// packages/ui/tailwind.config.js
2module.exports = {
3 content: [
4 './src/**/*.{js,jsx,ts,tsx}',
5 // Include workspace packages
6 '../shared/src/**/*.{js,jsx,ts,tsx}',
7 ],
8}JIT mode's killer feature is arbitrary values - use any value inline without configuration.
Use square brackets [] to specify arbitrary values:
1<!-- Arbitrary Widths -->
2<div class="w-[473px]">Exactly 473px</div>
3<div class="w-[calc(100%-2rem)]">Calculated width</div>
4<div class="w-[50vw]">50% of viewport width</div>
5
6<!-- Arbitrary Colors -->
7<div class="bg-[#1da1f2]">Hex color</div>
8<div class="bg-[rgb(255,0,0)]">RGB color</div>
9<div class="bg-[hsl(200,100%,50%)]">HSL color</div>
10<div class="text-[color:var(--my-color)]">CSS variable</div>
11
12<!-- Arbitrary Spacing -->
13<div class="p-[17px]">17px padding</div>
14<div class="m-[2.5rem]">2.5rem margin</div>
15<div class="gap-[clamp(1rem,5vw,3rem)]">Clamp gap</div>
16
17<!-- Arbitrary Grid -->
18<div class="grid-cols-[200px_1fr_200px]">Custom grid</div>
19<div class="grid-rows-[auto_1fr_auto]">Custom rows</div>
20
21<!-- Arbitrary Font Sizes -->
22<div class="text-[14px]">14px font size</div>
23<div class="text-[clamp(1rem,2.5vw,2rem)]">Fluid typography</div>
24
25<!-- Arbitrary Line Heights -->
26<div class="leading-[1.8]">1.8 line height</div>
27<div class="leading-[32px]">32px line height</div>
28
29<!-- Arbitrary Shadows -->
30<div class="shadow-[0_35px_60px_-15px_rgba(0,0,0,0.3)]">Custom shadow</div>
31
32<!-- Arbitrary Transforms -->
33<div class="translate-x-[123px]">Translate 123px</div>
34<div class="rotate-[17deg]">Rotate 17 degrees</div>
35<div class="scale-[1.23]">Scale 1.23</div>Use arbitrary CSS properties with [property:value]:
1<!-- Custom CSS Properties -->
2<div class="[mask-type:luminance]">Mask type</div>
3<div class="[appearance:textfield]">Appearance</div>
4<div class="[backdrop-filter:blur(20px)]">Backdrop filter</div>
5
6<!-- Complex Properties -->
7<div class="[clip-path:polygon(0_0,100%_0,100%_100%,0_100%)]">
8 Clip path
9</div>
10
11<div class="[text-shadow:0_2px_4px_rgba(0,0,0,0.1)]">
12 Text shadow
13</div>
14
15<!-- Grid Properties -->
16<div class="[grid-auto-flow:dense]">Dense grid flow</div>
17<div class="[grid-template-areas:'header_header'_'sidebar_main']">
18 Grid template areas
19</div>Create custom variants on-the-fly:
1<!-- Custom Pseudo-classes -->
2<div class="[&:nth-child(3)]:bg-blue-500">Third child</div>
3<div class="[&:not(:first-child)]:mt-4">Not first child</div>
4
5<!-- Custom Pseudo-elements -->
6<div class="[&::before]:content-['']">Before element</div>
7<div class="[&::after]:absolute">After element</div>
8
9<!-- Descendant Selectors -->
10<div class="[&>li]:text-blue-500">Direct li children</div>
11<div class="[&_a]:underline">All anchor descendants</div>
12
13<!-- Sibling Selectors -->
14<div class="[&+div]:mt-8">Adjacent sibling</div>
15<div class="[&~div]:mt-4">General sibling</div>
16
17<!-- Attribute Selectors -->
18<div class="[&[data-state='open']]:bg-blue-500">Data attribute</div>
19<div class="[&[aria-selected='true']]:font-bold">ARIA attribute</div>
20
21<!-- Media Query Variants -->
22<div class="[@media(min-width:800px)]:flex">Custom breakpoint</div>
23<div class="[@media(prefers-reduced-motion)]:transition-none">
24 Reduced motion
25</div>
26
27<!-- Supports Queries -->
28<div class="[@supports(display:grid)]:grid">Supports grid</div>Add ! to make utilities important:
1<!-- Force important -->
2<div class="!bg-red-500">Always red, overrides specificity</div>
3<div class="!p-4 !m-0">Important padding and margin</div>
4
5<!-- With arbitrary values -->
6<div class="!w-[473px]">Important custom width</div>
7
8<!-- With variants -->
9<div class="hover:!bg-blue-500">Important on hover</div>Use CSS variables with arbitrary values:
1/* Define CSS variables */
2:root {
3 --primary-color: #3b82f6;
4 --spacing-unit: 0.5rem;
5 --header-height: 64px;
6}1<!-- Use CSS variables with arbitrary values -->
2<div class="bg-[var(--primary-color)]">Primary background</div>
3<div class="p-[var(--spacing-unit)]">Variable padding</div>
4<div class="h-[var(--header-height)]">Variable height</div>
5
6<!-- Calculations with CSS variables -->
7<div class="h-[calc(100vh-var(--header-height))]">
8 Full height minus header
9</div>
10
11<!-- Color opacity with CSS variables -->
12<div class="bg-[rgb(var(--primary-color-rgb)/0.5)]">
13 Semi-transparent primary
14</div>✅ Good Use Cases:
❌ When to Avoid:
Better Alternative for Repeated Values:
1// Instead of using arbitrary values everywhere
2// <div class="w-[473px]">
3// <div class="w-[473px]">
4// <div class="w-[473px]">
5
6// Add to theme configuration
7module.exports = {
8 theme: {
9 extend: {
10 width: {
11 'sidebar': '473px',
12 },
13 },
14 },
15}
16
17// Then use
18// <div class="w-sidebar">Use backslash to escape special characters:
1<!-- Escape dots in class names -->
2<div class="top-\[2\.5rem\]">2.5rem from top</div>
3
4<!-- Escape slashes -->
5<div class="w-\[calc\(100\%-2rem\)\]">Calculated width</div>Or use underscores instead of spaces:
1<!-- Spaces become underscores -->
2<div class="grid-cols-[200px_minmax(900px,_1fr)_100px]">
3 Grid with minmax
4</div>Optimizing your Tailwind CSS for production ensures minimal bundle sizes and maximum performance.
Development Build:
1# Unminified with source maps
2npx tailwindcss -i ./src/input.css -o ./dist/output.css --watchProduction Build:
1# Minified and optimized
2NODE_ENV=production npx tailwindcss -i ./src/input.css -o ./dist/output.css --minifyPackage.json Scripts:
1{
2 "scripts": {
3 "dev": "tailwindcss -i ./src/input.css -o ./dist/output.css --watch",
4 "build": "NODE_ENV=production tailwindcss -i ./src/input.css -o ./dist/output.css --minify",
5 "build:analyze": "NODE_ENV=production tailwindcss -i ./src/input.css -o ./dist/output.css --minify && gzip -k dist/output.css"
6 }
7}Tailwind uses cssnano for minification:
1// postcss.config.js (if using PostCSS directly)
2module.exports = {
3 plugins: {
4 tailwindcss: {},
5 autoprefixer: {},
6 ...(process.env.NODE_ENV === 'production' ? {
7 cssnano: {
8 preset: ['default', {
9 discardComments: {
10 removeAll: true,
11 },
12 }],
13 },
14 } : {}),
15 },
16}Enable gzip or brotli compression on your server:
Nginx:
1# Enable gzip
2gzip on;
3gzip_vary on;
4gzip_types text/css application/javascript;
5gzip_min_length 1000;
6
7# Enable brotli (requires ngx_brotli module)
8brotli on;
9brotli_types text/css application/javascript;Express.js:
1const express = require('express');
2const compression = require('compression');
3
4const app = express();
5
6// Enable compression
7app.use(compression());
8
9app.use(express.static('public'));Typical Sizes:
1Uncompressed: 15-50 KB
2Gzipped: 5-15 KB
3Brotli: 4-12 KB1. Remove Unused Plugins:
1// tailwind.config.js
2
3// ❌ Including plugins you don't use
4module.exports = {
5 plugins: [
6 require('@tailwindcss/forms'),
7 require('@tailwindcss/typography'),
8 require('@tailwindcss/aspect-ratio'),
9 require('@tailwindcss/line-clamp'),
10 ],
11}
12
13// ✅ Only include what you need
14module.exports = {
15 plugins: [
16 require('@tailwindcss/forms'),
17 ],
18}2. Limit Color Palette:
1// If you only need a few colors
2module.exports = {
3 theme: {
4 colors: {
5 transparent: 'transparent',
6 current: 'currentColor',
7 white: '#ffffff',
8 black: '#000000',
9 blue: {
10 500: '#3b82f6',
11 600: '#2563eb',
12 },
13 gray: {
14 100: '#f3f4f6',
15 500: '#6b7280',
16 900: '#111827',
17 },
18 },
19 },
20}3. Limit Spacing Scale:
1// If you only need certain spacing values
2module.exports = {
3 theme: {
4 spacing: {
5 '0': '0',
6 '1': '0.25rem',
7 '2': '0.5rem',
8 '4': '1rem',
9 '6': '1.5rem',
10 '8': '2rem',
11 '12': '3rem',
12 '16': '4rem',
13 },
14 },
15}4. Disable Unused Core Plugins:
1module.exports = {
2 corePlugins: {
3 // Disable plugins you don't use
4 float: false,
5 objectFit: false,
6 objectPosition: false,
7 // ... etc
8 },
9}1. HTTP Caching:
1# Nginx - Cache CSS for 1 year
2location ~* \.css$ {
3 expires 1y;
4 add_header Cache-Control "public, immutable";
5}2. Content Hashing:
Use content hashing in filenames for cache busting:
1# Output with hash
2output.abc123def.css3. CDN Caching:
Serve CSS from a CDN for global distribution and caching.
1. Measure CSS Size:
1# Check file size
2ls -lh dist/output.css
3
4# Check gzipped size
5gzip -c dist/output.css | wc -c2. Analyze CSS:
1# Install cssstats
2npm install -g cssstats
3
4# Analyze your CSS
5cssstats dist/output.css3. Lighthouse Audits:
Run Lighthouse audits to measure CSS impact on performance.
1# Install Lighthouse
2npm install -g lighthouse
3
4# Run audit
5lighthouse https://your-site.com --view✅ Before Deploying:
--minify flag)NODE_ENV=productionTypical Production Build:
1# 1. Clean previous build
2rm -rf dist
3
4# 2. Build with production optimizations
5NODE_ENV=production npx tailwindcss -i ./src/input.css -o ./dist/output.css --minify
6
7# 3. Analyze size
8ls -lh dist/output.css
9gzip -c dist/output.css | wc -c
10
11# 4. Test locally
12npm run preview # or your preview commandNext.js:
Next.js automatically optimizes Tailwind CSS in production builds.
1// next.config.js - additional optimizations
2module.exports = {
3 experimental: {
4 optimizeCss: true, // Enable CSS optimization
5 },
6}Vite:
Vite handles CSS optimization automatically.
1// vite.config.js
2import { defineConfig } from 'vite';
3
4export default defineConfig({
5 build: {
6 cssCodeSplit: true, // Split CSS per component
7 minify: 'terser',
8 },
9});Webpack:
1// webpack.config.js
2const MiniCssExtractPlugin = require('mini-css-extract-plugin');
3const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');
4
5module.exports = {
6 optimization: {
7 minimizer: [
8 new CssMinimizerPlugin(),
9 ],
10 },
11 plugins: [
12 new MiniCssExtractPlugin({
13 filename: '[name].[contenthash].css',
14 }),
15 ],
16};Debugging JIT mode and resolving common issues to ensure your Tailwind setup works perfectly.
Symptom: Styles not appearing in browser
Causes & Solutions:
1. Content Path Incorrect:
1// ❌ Wrong
2content: ['./src/*.js'] // Doesn't match subdirectories
3
4// ✅ Correct
5content: ['./src/**/*.{js,jsx,ts,tsx}'] // Matches all subdirectories2. Dynamic Class Names:
1// ❌ Won't work - dynamic construction
2const size = 'large'
3<div className={`text-${size}`}>
4
5// ✅ Works - complete strings
6<div className={size === 'large' ? 'text-lg' : 'text-base'}>3. File Extension Not Included:
1// ❌ Missing .tsx
2content: ['./src/**/*.{js,jsx,ts}']
3
4// ✅ Includes all extensions
5content: ['./src/**/*.{js,jsx,ts,tsx}']Solutions:
1. Optimize Content Globs:
1// ❌ Too broad
2content: ['./**/*.{js,jsx}']
3
4// ✅ Specific paths
5content: [
6 './src/components/**/*.{js,jsx}',
7 './src/pages/**/*.{js,jsx}',
8]2. Exclude Build Artifacts:
1content: {
2 files: [
3 './src/**/*.{js,jsx,ts,tsx}',
4 ],
5 exclude: [
6 './dist/**',
7 './build/**',
8 './node_modules/**',
9 ],
10}3. Use Polling Instead of Watching (if needed):
1# Use polling for file watching (WSL, Docker)
2npx tailwindcss -i ./src/input.css -o ./dist/output.css --watch --pollSolutions:
1. Ensure Same Content Config:
Make sure development and production use the same content configuration.
2. Check Environment Variables:
1# Ensure NODE_ENV is set
2NODE_ENV=production npm run build3. Test Production Build Locally:
1# Build for production
2NODE_ENV=production npx tailwindcss build
3
4# Serve and test
5npx serve distSolutions:
1. Check Bracket Syntax:
1<!-- ❌ Wrong - missing brackets -->
2<div class="w-473px">
3
4<!-- ✅ Correct - with brackets -->
5<div class="w-[473px]">2. Escape Special Characters:
1<!-- ❌ Wrong - unescaped -->
2<div class="top-[2.5rem]">
3
4<!-- ✅ Correct - escaped (if needed in some contexts) -->
5<div class="top-\[2\.5rem\]">3. Use Correct Value Type:
1<!-- ❌ Wrong - incompatible value -->
2<div class="bg-[123px]"> <!-- color expects color value -->
3
4<!-- ✅ Correct - compatible value -->
5<div class="bg-[#ff0000]">1# Run with debug output
2DEBUG=* npx tailwindcss -i ./src/input.css -o ./dist/output.cssLook at the generated CSS file to see what's being generated:
1# View generated CSS
2cat dist/output.css | grep "your-class-name"Inspect elements to see if classes are:
1// Add logging to config
2module.exports = {
3 content: ['./src/**/*.{js,jsx,ts,tsx}'],
4 theme: {
5 extend: {},
6 },
7 plugins: [],
8}
9
10// Log config to verify
11console.log('Tailwind config loaded:', module.exports)Create a test file to verify class detection:
1<!-- test.html -->
2<!DOCTYPE html>
3<html>
4<head>
5 <link rel="stylesheet" href="output.css">
6</head>
7<body>
8 <!-- Test various class types -->
9 <div class="bg-blue-500">Standard class</div>
10 <div class="w-[473px]">Arbitrary value</div>
11 <div class="hover:bg-red-500">Variant</div>
12 <div class="dark:bg-gray-800">Dark mode</div>
13</body>
14</html>Add classes to safelist to force generation:
1module.exports = {
2 content: ['./src/**/*.{js,jsx,ts,tsx}'],
3 safelist: [
4 // Debug: force generate these classes
5 'bg-red-500',
6 'text-blue-600',
7 {
8 pattern: /bg-(red|blue|green)-(400|500|600)/,
9 },
10 ],
11}Measure Build Time:
1# Time the build
2time NODE_ENV=production npx tailwindcss -i ./src/input.css -o ./dist/output.css --minifyCheck File Sizes:
1# Uncompressed size
2ls -lh dist/output.css
3
4# Gzipped size
5gzip -c dist/output.css | wc -c
6
7# Brotli size (if available)
8brotli -c dist/output.css | wc -cLog Content Matches:
1// tailwind.config.js
2const glob = require('glob');
3
4const files = glob.sync('./src/**/*.{js,jsx,ts,tsx}');
5console.log(`Scanning ${files.length} files:`, files);
6
7module.exports = {
8 content: files,
9 // ... rest of config
10}Monitor Rebuilds:
1# Watch mode with verbose output
2npx tailwindcss -i ./src/input.css -o ./dist/output.css --watch
3# Rebuilds shown in terminal1. Check Documentation:
2. Search Issues:
3. Ask Community:
4. Minimal Reproduction:
When reporting bugs, create a minimal reproduction:
1project/
2├── package.json
3├── tailwind.config.js
4├── src/
5│ ├── input.css
6│ └── index.html
7└── dist/
8 └── output.cssInclude:
npm list tailwindcss)node --version)Build a complete, production-ready Tailwind CSS configuration with JIT mode, optimal content paths, arbitrary values, and performance optimizations.
1// tailwind.config.js - Production-Optimized Configuration
2/** @type {import('tailwindcss').Config} */
3module.exports = {
4 // Content configuration - tells JIT where to find classes
5 content: [
6 './src/pages/**/*.{js,ts,jsx,tsx}',
7 './src/components/**/*.{js,ts,jsx,tsx}',
8 './src/layouts/**/*.{js,ts,jsx,tsx}',
9 './src/app/**/*.{js,ts,jsx,tsx}',
10 './public/index.html',
11 ],
12
13 // Safelist for dynamic classes (use sparingly)
14 safelist: [
15 // Status colors generated dynamically
16 {
17 pattern: /bg-(red|green|yellow|blue)-(100|500|900)/,
18 variants: ['hover', 'focus'],
19 },
20 // Alert types
21 'alert-success',
22 'alert-error',
23 'alert-warning',
24 'alert-info',
25 ],
26
27 // Theme customization
28 theme: {
29 extend: {
30 // Custom colors
31 colors: {
32 brand: {
33 50: '#eff6ff',
34 100: '#dbeafe',
35 200: '#bfdbfe',
36 300: '#93c5fd',
37 400: '#60a5fa',
38 500: '#3b82f6', // Primary
39 600: '#2563eb',
40 700: '#1d4ed8',
41 800: '#1e40af',
42 900: '#1e3a8a',
43 },
44 },
45
46 // Custom spacing (if needed)
47 spacing: {
48 '128': '32rem',
49 '144': '36rem',
50 },
51
52 // Custom font family
53 fontFamily: {
54 sans: ['Inter var', 'system-ui', 'sans-serif'],
55 },
56
57 // Custom breakpoints (if needed)
58 screens: {
59 'xs': '475px',
60 '3xl': '1920px',
61 },
62 },
63 },
64
65 // Plugins
66 plugins: [
67 require('@tailwindcss/forms'),
68 require('@tailwindcss/typography'),
69 ],
70}
71
72// package.json - Build Scripts
73{
74 "name": "optimized-tailwind-project",
75 "version": "1.0.0",
76 "scripts": {
77 // Development - watch mode with fast rebuilds
78 "dev:css": "tailwindcss -i ./src/input.css -o ./dist/output.css --watch",
79
80 // Production - minified and optimized
81 "build:css": "NODE_ENV=production tailwindcss -i ./src/input.css -o ./dist/output.css --minify",
82
83 // Analyze - check bundle size
84 "analyze:css": "npm run build:css && ls -lh dist/output.css && gzip -c dist/output.css | wc -c",
85
86 // Full build
87 "build": "npm run build:css && npm run build:app"
88 },
89 "devDependencies": {
90 "tailwindcss": "^3.4.0",
91 "@tailwindcss/forms": "^0.5.7",
92 "@tailwindcss/typography": "^0.5.10",
93 "autoprefixer": "^10.4.16",
94 "postcss": "^8.4.32"
95 }
96}
97
98// postcss.config.js - PostCSS Configuration
99module.exports = {
100 plugins: {
101 tailwindcss: {},
102 autoprefixer: {},
103 // Minify in production
104 ...(process.env.NODE_ENV === 'production' ? {
105 cssnano: {
106 preset: ['default', {
107 discardComments: {
108 removeAll: true,
109 },
110 }],
111 },
112 } : {}),
113 },
114}
115
116// src/input.css - Input Stylesheet
117@tailwind base;
118@tailwind components;
119@tailwind utilities;
120
121/* Custom component classes (use sparingly with @apply) */
122@layer components {
123 .btn-primary {
124 @apply px-4 py-2 bg-brand-500 text-white rounded-lg font-semibold;
125 @apply hover:bg-brand-600 focus:outline-none focus:ring-2 focus:ring-brand-500 focus:ring-offset-2;
126 @apply transition-colors duration-200;
127 }
128
129 .card {
130 @apply bg-white rounded-lg shadow-md overflow-hidden;
131 }
132}
133
134/* Custom utilities */
135@layer utilities {
136 .text-shadow {
137 text-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
138 }
139
140 .text-shadow-lg {
141 text-shadow: 0 15px 30px rgba(0, 0, 0, 0.11), 0 5px 15px rgba(0, 0, 0, 0.08);
142 }
143}
144
145// Example usage in React component
146import React from 'react';
147
148function ProductCard({ product }) {
149 return (
150 <div className="card hover:shadow-xl transition-shadow duration-300">
151 {/* Image with arbitrary aspect ratio */}
152 <img
153 src={product.image}
154 alt={product.name}
155 className="w-full h-[240px] object-cover"
156 />
157
158 <div className="p-6">
159 {/* Title with custom text shadow */}
160 <h3 className="text-xl font-bold text-gray-900 text-shadow-lg mb-2">
161 {product.name}
162 </h3>
163
164 {/* Description with line clamp */}
165 <p className="text-gray-600 line-clamp-3 mb-4">
166 {product.description}
167 </p>
168
169 {/* Price with arbitrary color */}
170 <div className="flex items-center justify-between mb-4">
171 <span className="text-2xl font-bold text-[#2c5282]">
172 ${product.price}
173 </span>
174
175 {/* Status badge - dynamic class from safelist */}
176 <span className={`px-3 py-1 rounded-full text-sm font-medium ${
177 product.inStock
178 ? 'bg-green-100 text-green-900'
179 : 'bg-red-100 text-red-900'
180 }`}>
181 {product.inStock ? 'In Stock' : 'Out of Stock'}
182 </span>
183 </div>
184
185 {/* Action button using component class */}
186 <button className="btn-primary w-full">
187 Add to Cart
188 </button>
189 </div>
190 </div>
191 );
192}
193
194export default ProductCard;This complete example demonstrates production-ready JIT mode optimization:
1. Content Configuration:
2. Safelist Strategy:
3. Theme Customization:
extend object4. Build Scripts:
5. PostCSS Integration:
6. Component Classes:
.btn-primary with all states (hover, focus, transition).card base component7. Arbitrary Values in Practice:
h-[240px] for exact image heighttext-[#2c5282] for brand-specific color8. Performance Features:
Expected Results:
This setup balances flexibility, performance, and maintainability for production applications.
Now that you've mastered JIT mode and performance optimization, you're ready to:
Challenge: Optimize a Tailwind project to achieve < 10 KB gzipped CSS while maintaining full functionality. Measure before/after with Lighthouse!
Your progress is saved in this browser, on this device only.