Master production optimization techniques for Tailwind CSS including advanced purging strategies, critical CSS extraction, performance monitoring, CDN optimization, and deployment best practices for maximum performance in production environments.
Production optimization is crucial for delivering fast, efficient web applications. Tailwind CSS provides powerful tools and strategies for minimizing CSS bundle sizes, improving load times, and ensuring optimal performance in production environments.
In this lesson, you'll learn advanced techniques for optimizing Tailwind CSS in production, including content purging, critical CSS extraction, compression strategies, CDN optimization, and comprehensive performance monitoring. You'll also learn how to configure build pipelines and deployment workflows for maximum efficiency.
Tailwind's content purging is essential for production optimization. Understanding advanced purging strategies ensures minimal bundle sizes while preventing accidental removal of needed classes.
Comprehensive Content Configuration:
1// tailwind.config.js
2module.exports = {
3 content: {
4 // Core source files
5 files: [
6 './src/**/*.{js,jsx,ts,tsx}',
7 './pages/**/*.{js,jsx,ts,tsx}',
8 './components/**/*.{js,jsx,ts,tsx}',
9 './app/**/*.{js,jsx,ts,tsx}',
10 './public/**/*.html',
11 ],
12
13 // Advanced content extraction
14 extract: {
15 // Custom extraction for specific file types
16 html: (content) => {
17 return content.match(/[^<>"'`\s]*[^<>"'`\s:]/g) || [];
18 },
19
20 // Extract from template literals
21 js: (content) => {
22 const matches = content.match(
23 /[^<>"'`\s.(){}[\]#=%]*[^<>"'`\s.(){}[\]#=%:]/g
24 ) || [];
25 return matches;
26 },
27 },
28
29 // Transform content before extraction
30 transform: {
31 md: (content) => {
32 // Extract classes from markdown
33 return content.replace(/---[\s\S]*?---/, '');
34 },
35 },
36 },
37
38 // Safelist important classes
39 safelist: [
40 // Safelist patterns
41 {
42 pattern: /^bg-(red|green|blue)-(100|500|900)$/,
43 variants: ['hover', 'focus', 'active'],
44 },
45 // Dynamic grid classes
46 {
47 pattern: /^grid-cols-([1-9]|1[0-2])$/,
48 },
49 // Full class names
50 'text-center',
51 'font-bold',
52 // With variants
53 {
54 pattern: /^(bg|text)-/,
55 variants: ['lg', 'xl', '2xl'],
56 },
57 ],
58
59 // Blocklist rarely used utilities
60 blocklist: [
61 'container',
62 'cursor-help',
63 // Unused breakpoint variants
64 ],
65};Dynamic Class Handling:
1// ❌ BAD - Classes constructed dynamically won't be detected
2const buttonColor = 'blue';
3className={`bg-${buttonColor}-500`} // Won't be purged correctly
4
5// ✅ GOOD - Full class names present in code
6const colors = {
7 blue: 'bg-blue-500',
8 red: 'bg-red-500',
9 green: 'bg-green-500',
10};
11className={colors[buttonColor]}
12
13// ✅ GOOD - Template with full classes
14const getButtonClass = (color) => {
15 if (color === 'blue') return 'bg-blue-500';
16 if (color === 'red') return 'bg-red-500';
17 return 'bg-green-500';
18};
19
20// ✅ GOOD - Safelist dynamic patterns
21// In tailwind.config.js
22safelist: [
23 {
24 pattern: /bg-(blue|red|green)-(100|200|300|400|500|600|700|800|900)/,
25 },
26]Content Scanning Best Practices:
Critical CSS extraction identifies and inlines essential styles for above-the-fold content, dramatically improving perceived load times and Core Web Vitals scores.
Critical CSS Setup with Critters:
1// next.config.js (Next.js)
2const withCritters = require('critters-webpack-plugin');
3
4module.exports = {
5 webpack: (config, { dev, isServer }) => {
6 if (!dev && !isServer) {
7 config.plugins.push(
8 new withCritters({
9 // Extract critical CSS for above-the-fold content
10 preload: 'swap',
11 preloadFonts: true,
12 // Inline critical CSS
13 inlineFonts: true,
14 // Minimum file size to inline
15 minimumExternalSize: 0,
16 // Prune non-critical CSS
17 pruneSource: false,
18 })
19 );
20 }
21 return config;
22 },
23};Manual Critical CSS Extraction:
1// scripts/extract-critical-css.js
2const critical = require('critical');
3const glob = require('glob');
4
5async function extractCriticalCSS() {
6 const pages = glob.sync('dist/**/*.html');
7
8 for (const page of pages) {
9 await critical.generate({
10 inline: true,
11 base: 'dist/',
12 src: page,
13 target: {
14 html: page,
15 css: page.replace('.html', '-critical.css'),
16 },
17 width: 1300,
18 height: 900,
19 dimensions: [
20 {
21 width: 375,
22 height: 667,
23 },
24 {
25 width: 1920,
26 height: 1080,
27 },
28 ],
29 penthouse: {
30 blockJSRequests: false,
31 },
32 });
33
34 console.log(`✅ Critical CSS extracted for: ${page}`);
35 }
36}
37
38extractCriticalCSS();Resource Hints and Preloading:
1<!-- In your HTML <head> -->
2<!-- Preconnect to font providers -->
3<link rel="preconnect" href="https://fonts.googleapis.com">
4<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
5
6<!-- Preload critical fonts -->
7<link
8 rel="preload"
9 href="/fonts/inter-var.woff2"
10 as="font"
11 type="font/woff2"
12 crossorigin
13>
14
15<!-- Inline critical CSS -->
16<style>
17 /* Critical above-the-fold styles inlined here */
18 .header { /* ... */ }
19 .hero { /* ... */ }
20</style>
21
22<!-- Async load remaining CSS -->
23<link
24 rel="preload"
25 href="/styles/main.css"
26 as="style"
27 onload="this.onload=null;this.rel='stylesheet'"
28>
29<noscript>
30 <link rel="stylesheet" href="/styles/main.css">
31</noscript>Component-Level Code Splitting:
1// Lazy load non-critical components
2import { lazy, Suspense } from 'react';
3
4const HeavyComponent = lazy(() => import('./HeavyComponent'));
5
6function App() {
7 return (
8 <div>
9 {/* Critical content loads immediately */}
10 <Header />
11 <Hero />
12
13 {/* Non-critical content lazy loaded */}
14 <Suspense fallback={<div>Loading...</div>}>
15 <HeavyComponent />
16 </Suspense>
17 </div>
18 );
19}Proper compression and minification techniques can reduce CSS bundle sizes by 70-90%, significantly improving load times and reducing bandwidth costs.
PostCSS Production Pipeline:
1// postcss.config.js
2module.exports = {
3 plugins: {
4 'tailwindcss': {},
5 'autoprefixer': {},
6
7 // Production-only plugins
8 ...(process.env.NODE_ENV === 'production' ? {
9 // CSS Nano for aggressive minification
10 'cssnano': {
11 preset: ['advanced', {
12 // Discard comments
13 discardComments: {
14 removeAll: true,
15 },
16 // Reduce calc() expressions
17 calc: true,
18 // Merge adjacent rules
19 mergeRules: true,
20 // Normalize whitespace
21 normalizeWhitespace: true,
22 // Minify selectors
23 minifySelectors: true,
24 // Remove unused font weights
25 discardUnused: {
26 fontFace: true,
27 },
28 // Z-index optimization
29 zindex: false, // Keep original z-index values
30 }],
31 },
32
33 // PurgeCSS for additional unused CSS removal
34 '@fullhuman/postcss-purgecss': {
35 content: [
36 './src/**/*.{js,jsx,ts,tsx}',
37 './public/**/*.html',
38 ],
39 defaultExtractor: content => {
40 return content.match(/[\w-/:]+(?<!:)/g) || [];
41 },
42 safelist: {
43 standard: [/^data-/, /^aria-/],
44 deep: [/^swiper/],
45 greedy: [/tooltip$/],
46 },
47 },
48 } : {}),
49 },
50};Build Script with Compression:
1// scripts/build-css.js
2const postcss = require('postcss');
3const fs = require('fs').promises;
4const zlib = require('zlib');
5const { promisify } = require('util');
6
7const gzip = promisify(zlib.gzip);
8const brotli = promisify(zlib.brotliCompress);
9
10async function buildCSS() {
11 // Read source CSS
12 const css = await fs.readFile('src/styles/main.css', 'utf8');
13
14 // Process with PostCSS
15 const result = await postcss([
16 require('tailwindcss'),
17 require('autoprefixer'),
18 require('cssnano')({
19 preset: 'advanced',
20 }),
21 ]).process(css, { from: 'src/styles/main.css' });
22
23 // Write minified CSS
24 await fs.writeFile('dist/styles/main.css', result.css);
25
26 // Generate gzip version
27 const gzipped = await gzip(result.css, { level: 9 });
28 await fs.writeFile('dist/styles/main.css.gz', gzipped);
29
30 // Generate brotli version
31 const brotlied = await brotli(result.css, {
32 params: {
33 [zlib.constants.BROTLI_PARAM_QUALITY]: 11,
34 },
35 });
36 await fs.writeFile('dist/styles/main.css.br', brotlied);
37
38 // Size report
39 const originalSize = Buffer.byteLength(css);
40 const minifiedSize = Buffer.byteLength(result.css);
41 const gzipSize = gzipped.length;
42 const brotliSize = brotlied.length;
43
44 console.log('📊 CSS Build Report:');
45 console.log(`Original: ${(originalSize / 1024).toFixed(2)} KB`);
46 console.log(`Minified: ${(minifiedSize / 1024).toFixed(2)} KB (${((1 - minifiedSize / originalSize) * 100).toFixed(1)}% reduction)`);
47 console.log(`Gzip: ${(gzipSize / 1024).toFixed(2)} KB (${((1 - gzipSize / originalSize) * 100).toFixed(1)}% reduction)`);
48 console.log(`Brotli: ${(brotliSize / 1024).toFixed(2)} KB (${((1 - brotliSize / originalSize) * 100).toFixed(1)}% reduction)`);
49}
50
51buildCSS().catch(console.error);Server Configuration for Compression:
1# nginx.conf
2http {
3 # Gzip compression
4 gzip on;
5 gzip_vary on;
6 gzip_min_length 1024;
7 gzip_comp_level 6;
8 gzip_types
9 text/css
10 text/javascript
11 application/javascript
12 application/json;
13
14 # Brotli compression (if supported)
15 brotli on;
16 brotli_comp_level 6;
17 brotli_types
18 text/css
19 text/javascript
20 application/javascript
21 application/json;
22
23 # Cache static assets
24 location ~* \.(?:css|js)$ {
25 expires 1y;
26 add_header Cache-Control "public, immutable";
27 }
28}CDN optimization and intelligent caching strategies ensure fast global delivery of CSS assets while maintaining efficient cache invalidation.
CDN Configuration Strategy:
1// Build script with content hashing
2const crypto = require('crypto');
3
4function generateContentHash(content) {
5 return crypto
6 .createHash('md5')
7 .update(content)
8 .digest('hex')
9 .substring(0, 8);
10}
11
12async function buildWithHashing() {
13 const css = await fs.readFile('dist/styles/main.css', 'utf8');
14 const hash = generateContentHash(css);
15 const hashedFilename = `main.${hash}.css`;
16
17 // Write hashed file
18 await fs.writeFile(`dist/styles/${hashedFilename}`, css);
19
20 // Update HTML references
21 const html = await fs.readFile('dist/index.html', 'utf8');
22 const updatedHtml = html.replace(
23 'main.css',
24 hashedFilename
25 );
26 await fs.writeFile('dist/index.html', updatedHtml);
27
28 console.log(`✅ Generated: ${hashedFilename}`);
29}Cloudflare Workers for Edge Optimization:
1// cloudflare-worker.js
2addEventListener('fetch', event => {
3 event.respondWith(handleRequest(event.request));
4});
5
6async function handleRequest(request) {
7 const url = new URL(request.url);
8
9 // Cache CSS files aggressively
10 if (url.pathname.endsWith('.css')) {
11 const cache = caches.default;
12 let response = await cache.match(request);
13
14 if (!response) {
15 response = await fetch(request);
16
17 // Clone response with custom headers
18 response = new Response(response.body, response);
19 response.headers.set('Cache-Control', 'public, max-age=31536000, immutable');
20 response.headers.set('CDN-Cache-Control', 'max-age=31536000');
21
22 // Store in edge cache
23 event.waitUntil(cache.put(request, response.clone()));
24 }
25
26 return response;
27 }
28
29 return fetch(request);
30}Multi-CDN Strategy:
1// cdn-config.js
2const CDN_PROVIDERS = {
3 primary: 'https://cdn.cloudflare.com',
4 fallback: 'https://cdn.fastly.com',
5 backup: 'https://cdn.akamai.com',
6};
7
8function getCDNUrl(asset, provider = 'primary') {
9 const baseUrl = CDN_PROVIDERS[provider];
10 return `${baseUrl}/${asset}`;
11}
12
13// In your HTML generation
14function generateStylesheetLink(filename) {
15 return `
16 <link
17 rel="stylesheet"
18 href="${getCDNUrl(filename, 'primary')}"
19 onerror="this.onerror=null;this.href='${getCDNUrl(filename, 'fallback')}'"
20 >
21 `;
22}Cache Invalidation Strategy:
1// deploy-script.js
2async function deployWithCacheInvalidation() {
3 // Upload new CSS files
4 const files = await uploadToS3('dist/styles/*.css');
5
6 // Invalidate CloudFront cache
7 await invalidateCloudFront([
8 '/styles/main.*.css',
9 ]);
10
11 // Purge Cloudflare cache
12 await purgeCloudflare({
13 files: files.map(f => `https://example.com/${f}`),
14 });
15
16 console.log('✅ Deployment complete with cache invalidation');
17}Service Worker Caching:
1// service-worker.js
2const CACHE_NAME = 'css-v1';
3const CSS_FILES = [
4 '/styles/main.abc123.css',
5];
6
7self.addEventListener('install', event => {
8 event.waitUntil(
9 caches.open(CACHE_NAME)
10 .then(cache => cache.addAll(CSS_FILES))
11 );
12});
13
14self.addEventListener('fetch', event => {
15 if (event.request.url.endsWith('.css')) {
16 event.respondWith(
17 caches.match(event.request)
18 .then(response => response || fetch(event.request))
19 );
20 }
21});Comprehensive performance monitoring ensures your optimization efforts are effective and helps identify regressions before they impact users.
Web Vitals Monitoring:
1// performance-monitoring.js
2import { getCLS, getFID, getFCP, getLCP, getTTFB } from 'web-vitals';
3
4function sendToAnalytics({ name, delta, id, value }) {
5 // Send to your analytics service
6 gtag('event', name, {
7 event_category: 'Web Vitals',
8 event_label: id,
9 value: Math.round(name === 'CLS' ? delta * 1000 : delta),
10 non_interaction: true,
11 });
12
13 console.log(`📊 ${name}: ${value}`);
14}
15
16// Monitor all Core Web Vitals
17getCLS(sendToAnalytics);
18getFID(sendToAnalytics);
19getFCP(sendToAnalytics);
20getLCP(sendToAnalytics);
21getTTFB(sendToAnalytics);
22
23// CSS-specific metrics
24const cssLoadStart = performance.now();
25
26window.addEventListener('load', () => {
27 const cssLoadEnd = performance.now();
28 const cssLoadTime = cssLoadEnd - cssLoadStart;
29
30 console.log(`🎨 CSS Load Time: ${cssLoadTime.toFixed(2)}ms`);
31
32 // Get CSS file sizes
33 const cssResources = performance
34 .getEntriesByType('resource')
35 .filter(entry => entry.name.endsWith('.css'));
36
37 cssResources.forEach(resource => {
38 console.log(`📁 ${resource.name}:`);
39 console.log(` Transfer Size: ${(resource.transferSize / 1024).toFixed(2)} KB`);
40 console.log(` Duration: ${resource.duration.toFixed(2)}ms`);
41 });
42});Bundle Size Tracking:
1// scripts/track-bundle-size.js
2const fs = require('fs');
3const path = require('path');
4
5function getFileSize(filepath) {
6 const stats = fs.statSync(filepath);
7 return stats.size;
8}
9
10function trackBundleSize() {
11 const cssFile = 'dist/styles/main.css';
12 const size = getFileSize(cssFile);
13 const sizeKB = (size / 1024).toFixed(2);
14
15 // Store historical data
16 const history = JSON.parse(
17 fs.readFileSync('bundle-size-history.json', 'utf8')
18 );
19
20 const record = {
21 date: new Date().toISOString(),
22 size: size,
23 sizeKB: sizeKB,
24 commit: process.env.CI_COMMIT_SHA || 'local',
25 };
26
27 history.push(record);
28
29 // Calculate size change
30 if (history.length > 1) {
31 const previous = history[history.length - 2];
32 const change = size - previous.size;
33 const changePercent = ((change / previous.size) * 100).toFixed(2);
34
35 console.log(`📊 Bundle Size: ${sizeKB} KB`);
36 console.log(`📈 Change: ${change > 0 ? '+' : ''}${(change / 1024).toFixed(2)} KB (${changePercent}%)`);
37
38 // Fail build if size increased too much
39 if (change > 10240) { // 10KB threshold
40 console.error('❌ Bundle size increased by more than 10KB!');
41 process.exit(1);
42 }
43 }
44
45 fs.writeFileSync(
46 'bundle-size-history.json',
47 JSON.stringify(history, null, 2)
48 );
49}
50
51trackBundleSize();Lighthouse CI Integration:
1// lighthouserc.js
2module.exports = {
3 ci: {
4 collect: {
5 numberOfRuns: 3,
6 startServerCommand: 'npm run serve',
7 url: ['http://localhost:3000'],
8 },
9 assert: {
10 assertions: {
11 'categories:performance': ['error', { minScore: 0.9 }],
12 'first-contentful-paint': ['error', { maxNumericValue: 2000 }],
13 'largest-contentful-paint': ['error', { maxNumericValue: 2500 }],
14 'cumulative-layout-shift': ['error', { maxNumericValue: 0.1 }],
15 'total-blocking-time': ['error', { maxNumericValue: 300 }],
16 'render-blocking-resources': 'warn',
17 'unused-css-rules': ['warn', { maxLength: 0 }],
18 },
19 },
20 upload: {
21 target: 'temporary-public-storage',
22 },
23 },
24};Real User Monitoring (RUM):
1// rum-tracking.js
2class PerformanceTracker {
3 constructor() {
4 this.metrics = {};
5 this.init();
6 }
7
8 init() {
9 // Track CSS load performance
10 new PerformanceObserver((list) => {
11 for (const entry of list.getEntries()) {
12 if (entry.name.includes('.css')) {
13 this.trackCSSLoad(entry);
14 }
15 }
16 }).observe({ entryTypes: ['resource'] });
17
18 // Track layout shifts caused by CSS
19 new PerformanceObserver((list) => {
20 for (const entry of list.getEntries()) {
21 this.trackLayoutShift(entry);
22 }
23 }).observe({ entryTypes: ['layout-shift'] });
24 }
25
26 trackCSSLoad(entry) {
27 this.metrics.cssLoadTime = entry.duration;
28 this.metrics.cssTransferSize = entry.transferSize;
29 this.sendMetrics();
30 }
31
32 trackLayoutShift(entry) {
33 if (!entry.hadRecentInput) {
34 this.metrics.cls = (this.metrics.cls || 0) + entry.value;
35 this.sendMetrics();
36 }
37 }
38
39 sendMetrics() {
40 // Send to your analytics platform
41 if (typeof navigator.sendBeacon === 'function') {
42 navigator.sendBeacon('/api/metrics', JSON.stringify(this.metrics));
43 }
44 }
45}
46
47new PerformanceTracker();Font optimization is critical for performance, as fonts often block rendering. Proper font loading strategies prevent layout shifts and improve perceived performance.
Font Loading Strategies:
1/* Using @font-face with font-display */
2@font-face {
3 font-family: 'Inter';
4 src: url('/fonts/inter-var.woff2') format('woff2');
5 font-weight: 100 900;
6 font-display: swap; /* Show fallback immediately, swap when loaded */
7 font-style: normal;
8}
9
10/* Subset fonts for better performance */
11@font-face {
12 font-family: 'Inter';
13 src: url('/fonts/inter-latin.woff2') format('woff2');
14 unicode-range: U+0000-00FF, U+0131, U+0152-0153;
15 font-display: swap;
16}Preloading Critical Fonts:
1<!DOCTYPE html>
2<html>
3<head>
4 <!-- Preconnect to font providers -->
5 <link rel="preconnect" href="https://fonts.googleapis.com">
6 <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
7
8 <!-- Preload critical fonts -->
9 <link
10 rel="preload"
11 href="/fonts/inter-var.woff2"
12 as="font"
13 type="font/woff2"
14 crossorigin
15 >
16
17 <!-- Preload subset fonts -->
18 <link
19 rel="preload"
20 href="/fonts/inter-latin.woff2"
21 as="font"
22 type="font/woff2"
23 crossorigin
24 >
25
26 <!-- Optional: DNS prefetch for other resources -->
27 <link rel="dns-prefetch" href="https://cdn.example.com">
28</head>
29</html>Self-Hosted Font Optimization:
1// scripts/optimize-fonts.js
2const fontmin = require('fontmin');
3
4function optimizeFonts() {
5 const fontmin = new Fontmin()
6 .src('src/fonts/*.ttf')
7 .dest('dist/fonts')
8 .use(Fontmin.glyph({
9 // Only include characters you need
10 text: 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*()_+-=[]{}|;:,.<>?/',
11 hinting: false,
12 }))
13 .use(Fontmin.ttf2woff2());
14
15 fontmin.run((err, files) => {
16 if (err) throw err;
17 console.log('✅ Fonts optimized');
18 });
19}
20
21optimizeFonts();Variable Font Usage:
1/* Variable fonts reduce the number of font files */
2@font-face {
3 font-family: 'Inter Variable';
4 src: url('/fonts/inter-var.woff2') format('woff2-variations');
5 font-weight: 100 900;
6 font-style: oblique 0deg 10deg;
7 font-display: swap;
8}
9
10/* Use in Tailwind config */
11/* tailwind.config.js */
12module.exports = {
13 theme: {
14 fontFamily: {
15 sans: ['Inter Variable', 'sans-serif'],
16 },
17 },
18};Fallback Font Matching:
1/* Match fallback font metrics to reduce layout shift */
2@font-face {
3 font-family: 'Inter Fallback';
4 src: local('Arial');
5 ascent-override: 90%;
6 descent-override: 22%;
7 line-gap-override: 0%;
8 size-adjust: 107%;
9}
10
11body {
12 font-family: 'Inter', 'Inter Fallback', sans-serif;
13}A comprehensive production build pipeline that implements all optimization techniques including purging, compression, CDN deployment, and performance monitoring.
1// ===================================
2// Production Build Pipeline Configuration
3// ===================================
4
5// package.json
6{
7 "scripts": {
8 "build": "npm run build:css && npm run build:app && npm run optimize",
9 "build:css": "node scripts/build-css.js",
10 "build:app": "next build",
11 "optimize": "node scripts/optimize-production.js",
12 "analyze": "node scripts/analyze-bundle.js",
13 "deploy": "npm run build && node scripts/deploy.js"
14 },
15 "devDependencies": {
16 "tailwindcss": "^3.4.0",
17 "autoprefixer": "^10.4.16",
18 "postcss": "^8.4.32",
19 "cssnano": "^6.0.2",
20 "@fullhuman/postcss-purgecss": "^5.0.0",
21 "critters": "^0.0.20",
22 "web-vitals": "^3.5.0"
23 }
24}
25
26// ===================================
27// tailwind.config.js - Production Optimized
28// ===================================
29
30module.exports = {
31 content: {
32 files: [
33 './src/**/*.{js,jsx,ts,tsx}',
34 './app/**/*.{js,jsx,ts,tsx}',
35 './components/**/*.{js,jsx,ts,tsx}',
36 ],
37 extract: {
38 js: (content) => {
39 return content.match(/[^<>"'`\s.(){}[\]#=%]*[^<>"'`\s.(){}[\]#=%:]/g) || [];
40 },
41 },
42 },
43
44 safelist: [
45 {
46 pattern: /^(bg|text|border)-(red|green|blue|yellow)-(100|500|900)$/,
47 variants: ['hover', 'focus'],
48 },
49 {
50 pattern: /^grid-cols-([1-9]|1[0-2])$/,
51 },
52 ],
53
54 theme: {
55 extend: {
56 fontFamily: {
57 sans: ['Inter Variable', 'Inter Fallback', 'sans-serif'],
58 },
59 },
60 },
61
62 plugins: [],
63};
64
65// ===================================
66// postcss.config.js - Production Pipeline
67// ===================================
68
69module.exports = {
70 plugins: {
71 'tailwindcss': {},
72 'autoprefixer': {},
73
74 ...(process.env.NODE_ENV === 'production' ? {
75 'cssnano': {
76 preset: ['advanced', {
77 discardComments: { removeAll: true },
78 calc: true,
79 mergeRules: true,
80 normalizeWhitespace: true,
81 minifySelectors: true,
82 zindex: false,
83 }],
84 },
85
86 '@fullhuman/postcss-purgecss': {
87 content: ['./src/**/*.{js,jsx,ts,tsx}'],
88 defaultExtractor: content => {
89 return content.match(/[\w-/:]+(?<!:)/g) || [];
90 },
91 safelist: {
92 standard: [/^data-/, /^aria-/],
93 },
94 },
95 } : {}),
96 },
97};
98
99// ===================================
100// scripts/build-css.js - CSS Build with Compression
101// ===================================
102
103const postcss = require('postcss');
104const fs = require('fs').promises;
105const zlib = require('zlib');
106const crypto = require('crypto');
107const { promisify } = require('util');
108
109const gzip = promisify(zlib.gzip);
110const brotli = promisify(zlib.brotliCompress);
111
112async function buildCSS() {
113 console.log('🎨 Building production CSS...');
114
115 // Read source
116 const css = await fs.readFile('src/styles/main.css', 'utf8');
117 const originalSize = Buffer.byteLength(css);
118
119 // Process with PostCSS
120 const result = await postcss([
121 require('tailwindcss'),
122 require('autoprefixer'),
123 require('cssnano')({ preset: 'advanced' }),
124 ]).process(css, { from: 'src/styles/main.css' });
125
126 const minifiedSize = Buffer.byteLength(result.css);
127
128 // Generate content hash
129 const hash = crypto.createHash('md5')
130 .update(result.css)
131 .digest('hex')
132 .substring(0, 8);
133
134 const filename = `main.${hash}.css`;
135
136 // Write files
137 await fs.writeFile(`dist/styles/${filename}`, result.css);
138
139 // Compress
140 const gzipped = await gzip(result.css, { level: 9 });
141 await fs.writeFile(`dist/styles/${filename}.gz`, gzipped);
142
143 const brotlied = await brotli(result.css, {
144 params: { [zlib.constants.BROTLI_PARAM_QUALITY]: 11 },
145 });
146 await fs.writeFile(`dist/styles/${filename}.br`, brotlied);
147
148 // Report
149 console.log('\n📊 CSS Build Report:');
150 console.log(`✅ Original: ${(originalSize / 1024).toFixed(2)} KB`);
151 console.log(`✅ Minified: ${(minifiedSize / 1024).toFixed(2)} KB (${((1 - minifiedSize / originalSize) * 100).toFixed(1)}% reduction)`);
152 console.log(`✅ Gzip: ${(gzipped.length / 1024).toFixed(2)} KB (${((1 - gzipped.length / originalSize) * 100).toFixed(1)}% reduction)`);
153 console.log(`✅ Brotli: ${(brotlied.length / 1024).toFixed(2)} KB (${((1 - brotlied.length / originalSize) * 100).toFixed(1)}% reduction)\n`);
154
155 return { filename, size: minifiedSize };
156}
157
158buildCSS().catch(console.error);
159
160// ===================================
161// scripts/optimize-production.js - Post-Build Optimization
162// ===================================
163
164const critical = require('critical');
165const glob = require('glob');
166
167async function optimizeProduction() {
168 console.log('🚀 Running production optimizations...');
169
170 // Extract critical CSS
171 const pages = glob.sync('dist/**/*.html');
172
173 for (const page of pages) {
174 await critical.generate({
175 inline: true,
176 base: 'dist/',
177 src: page,
178 target: { html: page },
179 width: 1300,
180 height: 900,
181 dimensions: [
182 { width: 375, height: 667 },
183 { width: 1920, height: 1080 },
184 ],
185 });
186
187 console.log(`✅ Critical CSS extracted for: ${page}`);
188 }
189
190 console.log('✅ Production optimization complete!');
191}
192
193optimizeProduction().catch(console.error);
194
195// ===================================
196// src/lib/performance-monitoring.js
197// ===================================
198
199import { getCLS, getFID, getFCP, getLCP, getTTFB } from 'web-vitals';
200
201function sendToAnalytics({ name, delta, id, value }) {
202 const body = JSON.stringify({ name, delta, id, value });
203
204 if (navigator.sendBeacon) {
205 navigator.sendBeacon('/api/metrics', body);
206 }
207
208 console.log(`📊 ${name}: ${value.toFixed(2)}`);
209}
210
211// Monitor all Core Web Vitals
212getCLS(sendToAnalytics);
213getFID(sendToAnalytics);
214getFCP(sendToAnalytics);
215getLCP(sendToAnalytics);
216getTTFB(sendToAnalytics);
217
218// Track CSS load time
219const cssLoadObserver = new PerformanceObserver((list) => {
220 for (const entry of list.getEntries()) {
221 if (entry.name.includes('.css')) {
222 console.log(`🎨 CSS Load: ${entry.duration.toFixed(2)}ms`);
223 console.log(`📦 Size: ${(entry.transferSize / 1024).toFixed(2)} KB`);
224 }
225 }
226});
227
228cssLoadObserver.observe({ entryTypes: ['resource'] });
229
230// ===================================
231// HTML with Optimized Loading
232// ===================================
233
234<!DOCTYPE html>
235<html lang="en">
236<head>
237 <meta charset="UTF-8">
238 <meta name="viewport" content="width=device-width, initial-scale=1.0">
239
240 <!-- Preconnect -->
241 <link rel="preconnect" href="https://fonts.googleapis.com">
242 <link rel="preconnect" href="https://cdn.example.com">
243
244 <!-- Preload critical font -->
245 <link
246 rel="preload"
247 href="/fonts/inter-var.woff2"
248 as="font"
249 type="font/woff2"
250 crossorigin
251 >
252
253 <!-- Critical CSS inlined by Critters -->
254 <style>
255 /* Critical above-the-fold styles */
256 </style>
257
258 <!-- Async load remaining CSS -->
259 <link
260 rel="preload"
261 href="/styles/main.abc123.css"
262 as="style"
263 onload="this.onload=null;this.rel='stylesheet'"
264 >
265 <noscript>
266 <link rel="stylesheet" href="/styles/main.abc123.css">
267 </noscript>
268
269 <title>Production Optimized App</title>
270</head>
271<body>
272 <div id="root"></div>
273
274 <!-- Performance monitoring -->
275 <script type="module" src="/js/performance-monitoring.js"></script>
276</body>
277</html>Now that you've mastered production optimization for Tailwind CSS, you're ready to learn best practices and advanced patterns in the final lesson. This will include maintainability strategies, team workflows, and building scalable design systems with Tailwind CSS.
Try the interactive examples to see different optimization techniques in action!
Your progress is saved in this browser, on this device only.