Optimización del rendimiento de Tailwind: JIT, configuración de content y control del tamaño en producción

Abres Chrome DevTools y ves un archivo CSS de 3,5 MB.
Una página nueva subió el tiempo de carga de 800 ms a 3,2 s. Tras revisar todo, el problema estaba en ese CSS: lleno de clases de Tailwind que ni siquiera se usaban.
Tailwind presume de ser «amigable con el rendimiento». ¿Cómo puede frenarte?
Resulta que el problema no es Tailwind, sino la configuración. Si entiendes cómo funciona el modo JIT, ajustas bien content y aplicas varias capas de optimización en producción, el CSS puede pasar de MB a KB. El sitio de Netflix, por ejemplo, usa solo 6,5 KB.
Hoy repasamos los errores que cometí.
1. Modo JIT: la revolución del rendimiento en Tailwind
1.1 El problema del modo tradicional
Antes de esa noche con el CSS de 3,5 MB, mi idea de Tailwind se quedaba en «un framework CSS utility-first».
Luego supe que el «modo tradicional» de Tailwind v2 generaba de antemano todas las combinaciones posibles de clases: todos los colores, todos los espaciados, todas las variantes (hover, focus, disabled, etc.). En un proyecto de complejidad media, el CSS de desarrollo podía superar los 10 MB.
Un CSS de 10 MB en local parece inofensivo: el ancho de banda no es el problema. Pero el navegador debe parsear una hoja de estilos enorme, lo que afecta la memoria y el rendimiento de DevTools.
Yo mismo lo viví depurando en Firefox: al cambiar una clase, DevTools se congelaba varios segundos. Recargar la página era desesperante.
Peor aún, en modo tradicional daba miedo tocar la configuración. Añadir un breakpoint o activar focus-visible implicaba calcular «cuántas combinaciones más generará esto». Al final, el equipo elegía entre rendimiento y flexibilidad.
1.2 Cómo funciona realmente el JIT
El modo JIT (Just-in-Time) llegó en Tailwind v2.1 y es el predeterminado desde v3+. En resumen: generación bajo demanda.
El modo tradicional pregenera en el build todas las combinaciones posibles, aunque no las uses. JIT hace lo contrario: escanea tus plantillas (HTML, JSX, Vue, etc.), detecta qué clases usas realmente y solo genera esos estilos.
Un ejemplo. En modo tradicional, Tailwind generaría CSS como este:
.bg-black { background-color: #000 }
.hover\:bg-black:hover { background-color: #000 }
.focus\:bg-black:focus { background-color: #000 }
.disabled\:bg-black:disabled { background-color: #000 }
/* ... y decenas de combinaciones de variantes */
Aunque tu proyecto solo use bg-black, se generan todas las demás variantes.
Con JIT es distinto. Escanea las plantillas, ve que solo usas bg-black y hover:bg-black, y genera solo estas dos:
.bg-black { background-color: #000 }
.hover\:bg-black:hover { background-color: #000 }
Así, el CSS de desarrollo pasa de 10 MB a KB, y coincide con el de producción.
1.3 Beneficios reales del JIT
La primera vez que activé JIT en un proyecto, la recarga fue tan rápida que parecía irreal: antes esperaba varios segundos; ahora, casi al instante.
Velocidad de build: antes un build completo tardaba 2-3 minutos; ahora, segundos. JIT no pregenera todos los estilos, solo escanea plantillas y extrae clases.
Experiencia de desarrollo: DevTools dejó de congelarse. Antes, al cambiar una clase, el navegador reparseaba 10 MB de CSS; ahora, con unos pocos KB, el cambio es casi inmediato.
Valores arbitrarios: una sorpresa agradable. En modo tradicional, usar text-[#facc15] exigía safelist en la configuración. JIT lo soporta directamente:
// No hace falta predefinirlo en la configuración, úsalo directamente
<h1 class="text-[2.5rem] mt-[1.35rem] text-[#facc15]">
JIT lo simplifica todo
</h1>
Clases dinámicas: en modo tradicional, concatenaciones como 'text-' + color no se detectaban. JIT también tiene límites, pero con safelist gestiona mejor los escenarios dinámicos.
¿Cómo activar JIT? En Tailwind v3+ ya es el predeterminado. Si sigues en v2, actívalo así:
// tailwind.config.js
module.exports = {
mode: 'jit', // v2 requiere activación manual
content: ['./src/**/*.{html,js,jsx,ts,tsx}'],
// ...
}
2. Configuración de content: la clave del escaneo preciso
JIT funciona bien solo si content está bien configurado.
content define qué archivos escanea Tailwind para extraer clases. Si falla, pierdes estilos (alcance demasiado estrecho) o inflas el CSS (alcance demasiado amplio).
2.1 Fundamentos de content
La sintaxis básica es sencilla:
// tailwind.config.js
module.exports = {
content: [
'./src/**/*.{html,js,jsx,ts,tsx}', // Escanea todas las plantillas en src
],
// ...
}
Aquí ** significa cualquier nivel de directorio y *.{html,js,jsx,ts,tsx} coincide con esas extensiones.
Según el framework, puede que debas ajustar las rutas:
// Proyecto Next.js
content: [
'./pages/**/*.{js,ts,jsx,tsx}',
'./components/**/*.{js,ts,jsx,tsx}',
'./app/**/*.{js,ts,jsx,tsx}', // App Router
]
// Proyecto Astro
content: [
'./src/**/*.{astro,html,js,jsx,ts,tsx}',
]
Lo esencial: cubrir todos los archivos que usan clases de Tailwind. Si omites un directorio, los estilos de ese directorio no se generarán.
2.2 Errores que cometí
Error 1: patrones glob demasiado amplios
Quise simplificar la configuración y usé esto:
content: [
'./**/*.js', // ¡Esto escanea node_modules!
]
Resultado: Tailwind escaneó todos los JS de node_modules, el build se disparó y generó montones de estilos sin sentido.
La solución es limitar el alcance:
content: [
'./src/**/*.js', // Solo escanea src
'./components/**/*.js', // Solo escanea components
]
Error 2: omitir directorios de componentes
En una refactorización moví componentes a ./lib/components/. Olvidé actualizar content y los estilos de la nueva ubicación desaparecieron.
Tardé horas en encontrarlo. La lección: al cambiar la estructura del proyecto, actualiza siempre content.
Error 3: clases construidas dinámicamente
Con código como este:
const color = 'red';
const className = `text-${color}-500`; // JIT no puede detectarlo
JIT ve una cadena de plantilla, no un nombre de clase completo. En producción, ese estilo se elimina.
Solución: safelist (más adelante) o sintaxis por objetos:
const colors = {
red: 'text-red-500',
blue: 'text-blue-500',
};
const className = colors[color]; // Nombre completo, detectable
2.3 safelist y clases dinámicas
Algunos escenarios sí necesitan clases dinámicas. Ahí entra safelist.
// tailwind.config.js
module.exports = {
safelist: [
'text-red-500',
'text-blue-500',
'bg-red-500',
// O usa regex para coincidir con un grupo de clases
{
pattern: /text-(red|blue|green)-(500|600)/,
variants: ['hover', 'focus'], // Conserva también las variantes
},
],
}
safelist fuerza a Tailwind a generar esos estilos aunque no aparezcan directamente en las plantillas.
Pero ojo: cuantas más entradas en safelist, mayor el CSS. Úsalo solo cuando sea necesario; no lo conviertas en una «caja fuerte» con todas las clases posibles.
3. Control del tamaño en producción: estrategia de optimización en cuatro capas
JIT hace posible un CSS pequeño en desarrollo, pero el build de producción necesita más optimización.
Organicé una estrategia en cuatro capas, de configuración a compresión.
3.1 Capa 1: configuración precisa de content
Es la base, ya explicada.
Principio clave: solo escanear archivos que realmente usan clases de Tailwind. Cuanto más preciso el alcance, más rápido el build y menor el CSS.
// Bien: alcance preciso
content: [
'./src/components/**/*.jsx',
'./src/pages/**/*.tsx',
]
// Mal: alcance demasiado amplio
content: [
'./**/*.js', // Escanea node_modules
]
3.2 Capa 2: eliminación automática con PurgeCSS
Tailwind v3+ activa PurgeCSS automáticamente en producción y elimina estilos no usados.
Lo clave es distinguir bien desarrollo y producción en los comandos de build:
# Build de desarrollo (no elimina estilos no usados)
npm run dev
# Build de producción (PurgeCSS automático)
npm run build
Con PostCSS, puedes controlarlo explícitamente:
// postcss.config.js
module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {},
...(process.env.NODE_ENV === 'production' ? { cssnano: {} } : {}),
},
}
3.3 Capa 3: Minify con cssnano
Tras PurgeCSS, cssnano comprime aún más el CSS.
Incluye: eliminar comentarios, fusionar reglas duplicadas, simplificar selectores, comprimir valores, etc.
# Minify directo con Tailwind CLI
npx tailwindcss -i ./src/input.css -o ./dist/output.css --minify
# O vía PostCSS (como arriba)
Dato real de mi proyecto: de 150 KB (tras PurgeCSS) a 45 KB (tras minify).
3.4 Capa 4: compresión de red Brotli/Gzip
Esta capa no optimiza el CSS en sí, sino la transferencia por red.
Con Brotli o Gzip en el servidor, el CSS puede reducirse otro 60-80 %.
# Configuración Brotli en Nginx (requiere el módulo ngx_brotli)
brotli on;
brotli_comp_level 6;
brotli_types text/css application/javascript;
# O Gzip (soporte predeterminado en Nginx)
gzip on;
gzip_comp_level 6;
gzip_types text/css application/javascript;
Comparación: un CSS de 45 KB queda en ~8 KB tras Brotli.
Cuando Netflix dice que su CSS pesa 6,5 KB, se refiere al volumen de transferencia comprimido con Brotli, no al tamaño original del archivo.
4. Casos reales y comparación de datos
4.1 Mi comparación antes y después de optimizar
Sinceramente, al ver estos números hasta yo me sorprendí.
4.2 Resolución de problemas frecuentes
¿Faltan estilos?
Primero: revisa content y confirma que todos los archivos con clases de Tailwind están en el alcance.
Segundo: si hay clases dinámicas, comprueba si necesitas safelist.
Tercero: verifica el comando de build; usa producción (npm run build), no desarrollo.
¿El CSS sigue siendo grande?
Revisa si safelist es excesivo. Comprueba si CSS de bibliotecas de terceros se mezcla en el output de Tailwind. Revisa si el alcance de content es demasiado amplio.
¿DevTools se congela?
Confirma Tailwind ≥3 (JIT por defecto). Comprueba que content sea preciso. Si sigues en v2, actualiza o activa JIT manualmente.
5. Novedades de Tailwind v4
Tras las optimizaciones actuales, veamos Tailwind v4 (publicado a finales de 2024).
5.1 Motor Oxide
La actualización más emocionante: Tailwind reescribió el motor subyacente en Rust.
Dato oficial: la velocidad de build incremental sube 182 veces. Antes, cambiar una clase implicaba esperar segundos a la recompilación; ahora, respuesta en milisegundos.
El principio: Oxide cachea los resultados del escaneo y solo reprocesa lo modificado. Muy útil en proyectos grandes: tenemos uno con más de 200 archivos de componentes; antes cada build tardaba ~10 s; ahora, casi al instante.
5.2 Objetivo de configuración cero
Tailwind v4 promueve una idea nueva: la mayoría de proyectos no necesitan tailwind.config.js.
La configuración predeterminada ya cubre lo habitual: CSS moderno, container queries, breakpoints razonables, sistema de colores completo. Solo escribes configuración si necesitas personalización profunda.
Eso acelera el arranque de proyectos nuevos y reduce errores de configuración.
Al migrar a v4, ten en cuenta que la sintaxis cambió. Por ejemplo, el antiguo theme.extend.colors ahora usa propiedades personalizadas CSS. Revisa la guía oficial de migración antes de actualizar.
Resumen
Tras aquel colapso a las tres de la madrugada, reorganicé la configuración de Tailwind del proyecto desde cero.
JIT hace que desarrollo y producción usen el mismo CSS pequeño; content define el alcance del escaneo; la optimización en cuatro capas comprime el CSS de MB a KB. Con el motor Oxide de Tailwind v4, la velocidad de build deja de ser un cuello de botella.
Si tu proyecto sigue en modo tradicional o tienes dudas con content, revisa estos puntos:
- ¿Tailwind ≥3? (JIT activado por defecto)
- ¿
contentcubre con precisión todas las plantillas? - ¿El build de producción activa PurgeCSS y cssnano?
- ¿El servidor tiene Brotli/Gzip configurado?
Tras estos cambios, lo más probable es que veas una mejora clara de rendimiento.
Si tienes dudas, deja un comentario. También puedes consultar la documentación oficial de Tailwind sobre JIT y optimización en producción; está muy clara.
Referencias
- Documentación del modo Just-in-Time de Tailwind CSS
- Documentación Optimizing for Production
- Just-In-Time: The Next Generation of Tailwind CSS
- Anuncio de Tailwind CSS v4.0
- Tailwind CSS Best Practices for Performance Optimization
Configuración de optimización del rendimiento de Tailwind CSS
Flujo completo de configuración desde la activación del modo JIT hasta el control del tamaño en producción
⏱️ Estimated time: 30 min
- 1
Step 1: Confirmar la versión de Tailwind
Comprueba la versión de Tailwind CSS del proyecto:
• Tailwind v3+ activa el modo JIT por defecto
• Tailwind v2 requiere añadir manualmente mode: 'jit' en la configuración
• Se recomienda actualizar a v3+ para obtener el mejor rendimiento - 2
Step 2: Configurar las rutas de escaneo de content
Define rutas de escaneo precisas en tailwind.config.js:
• Next.js: ['./pages/**/*.{js,ts,jsx,tsx}', './components/**/*.{js,ts,jsx,tsx}']
• Astro: ['./src/**/*.{astro,html,js,jsx,ts,tsx}']
• Evita patrones glob demasiado amplios como './**/*.js' - 3
Step 3: Gestionar nombres de clase dinámicos
Para clases construidas dinámicamente, usa safelist para forzar su generación:
• Añade nombres de clase completos en el array safelist
• Usa pattern con expresiones regulares para coincidir con grupos de clases
• Combina con variants para conservar hover, focus, etc. - 4
Step 4: Configurar la optimización del build de producción
Añade cssnano en la configuración de PostCSS:
• No actives cssnano en desarrollo (builds más rápidos)
• Activa automáticamente cssnano minify en producción
• Usa process.env.NODE_ENV para controlar la condición de activación - 5
Step 5: Configurar la compresión del servidor
Configura compresión Brotli o Gzip en Nginx:
• Brotli: brotli on; brotli_comp_level 6;
• Gzip: gzip on; gzip_comp_level 6;
• Ambos deben incluir los tipos text/css y application/javascript
FAQ
¿Cómo se activa el modo JIT en Tailwind v3?
¿Qué problemas causa omitir archivos en la configuración de content?
¿Por qué el JIT omite nombres de clase dinámicos?
¿Los 6,5 KB de CSS de Netflix son el tamaño original o el comprimido?
¿Qué mejoras aporta el motor Oxide de Tailwind v4?
¿Qué impacto tiene configurar demasiadas entradas en safelist?
10 min de lectura · Publicado el: 30 mar 2026 · Actualizado el: 21 ago 2026
Tailwind y shadcn/ui en práctica
Si llegaste desde búsqueda, lo más rápido es ir al artículo anterior o siguiente de esta misma serie.
Anterior
Dialog, Sheet y Popover: accesibilidad y gestión del foco en capas modales
Análisis profundo de la accesibilidad y la gestión del foco en Dialog, Sheet y Popover de shadcn/ui: estándares WCAG, atributos ARIA, navegación por teclado y trampa de foco, con ejemplos de código completos
Parte 10 de 14
Siguiente
Astro + Tailwind: configurar estilos sin conflictos con los componentes islands
¿Conflictos de estilos Tailwind CSS con la arquitectura islands de Astro? Esta guía explica astro-island/astro-slot, la integración correcta de Tailwind v4 y cuatro escenarios habituales de conflictos CSS con sus soluciones
Parte 12 de 14



Comentarios
Inicia sesión con GitHub para dejar un comentario