Cambiar tema

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

Easton editorial illustration: signal tracing instrument

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.

10 MB+
Tamaño del CSS de desarrollo en modo tradicional

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-$&#123;color&#125;-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.

Capa 1
Configuración precisa de content
Solo escanea archivos realmente usados
Capa 2
Eliminación con PurgeCSS
Borra estilos no usados automáticamente
Capa 3
Minify con cssnano
Compresión y optimización de CSS
Capa 4
Brotli/Gzip
Compresión en la transferencia de red
Source: Capas de la estrategia de optimizació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;
6,5 KB
Volumen de transferencia del CSS de Netflix

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

3,5 MB → 28 KB
CSS de build de desarrollo
Antes y después del modo JIT
320 KB → 48 KB
CSS de build de producción
Tamaño sin comprimir
7,2 KB
Volumen final de transferencia
Tras compresión Brotli
3,2 s → 820 ms
Lighthouse LCP
Rendimiento de carga de página
Source: Datos medidos

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.

182x
Mejora en velocidad de build incremental

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:

  1. ¿Tailwind ≥3? (JIT activado por defecto)
  2. ¿content cubre con precisión todas las plantillas?
  3. ¿El build de producción activa PurgeCSS y cssnano?
  4. ¿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

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. 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. 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. 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. 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. 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?
Tailwind v3+ activa el modo JIT por defecto, sin configuración adicional. Si aún usas Tailwind v2, añade mode: 'jit' en tailwind.config.js.
¿Qué problemas causa omitir archivos en la configuración de content?
Si omites archivos, las clases de Tailwind en esos archivos no generarán estilos y desaparecerán tras el build de producción. Al cambiar la estructura del proyecto, actualiza siempre la configuración de content.
¿Por qué el JIT omite nombres de clase dinámicos?
JIT escanea el texto del código fuente. Nombres concatenados dinámicamente como `text-$&#123;color&#125;-500` aparecen como cadenas de plantilla en el código, no como nombres de clase completos, y no pueden detectarse. Usa safelist o cambia a sintaxis de mapeo por objetos.
¿Los 6,5 KB de CSS de Netflix son el tamaño original o el comprimido?
6,5 KB es el volumen de transferencia en red tras compresión Brotli. El archivo CSS original, tras PurgeCSS y cssnano, ronda las decenas de KB; tras Brotli/Gzip, el volumen de transferencia se reduce drásticamente.
¿Qué mejoras aporta el motor Oxide de Tailwind v4?
El motor Oxide está reescrito en Rust y acelera las compilaciones incrementales 182 veces. Su principio: cachea los resultados del escaneo de archivos y solo reprocesa las partes modificadas; en proyectos grandes, el build pasa de ~10 s a ~1 s.
¿Qué impacto tiene configurar demasiadas entradas en safelist?
safelist fuerza la generación de estilos específicos. Demasiadas entradas inflan el archivo CSS. Úsalo solo en escenarios con clases dinámicas necesarias; no añadas todas las clases que podrías usar.

10 min de lectura · Publicado el: 30 mar 2026 · Actualizado el: 21 ago 2026

Comentarios

Inicia sesión con GitHub para dejar un comentario

Easton BlogEaston Blog