Skip to main content
Volver al blog Copiar Markdown
· 8 min de lectura ·
tailwindcss css frontend

Tailwind CSS 4: Motor en Rust y Rebuilds en Microsegundos

Tailwind CSS 4 reescribe el motor en Rust: builds ~5x más rápidos, rebuilds 100x+ más rápidos. Lo que se rompió al migrar 3 proyectos y el setup que funciona. →

Óscar Gallego

Óscar Gallego

Desarrollador Web

Tailwind CSS 4 performance - Rust engine optimization illustration
En este artículo

Tailwind CSS 4 no es una actualización menor. Es una reescritura completa del motor en Rust, y rompe la compatibilidad con v3 en sitios que te van a costar una tarde cada uno.

Migré 3 proyectos de producción. La velocidad es real. La rotura también.

Esto es lo que nadie te cuenta.

Qué cambia en v4, en 30 segundos

El framework se ha reconstruido alrededor de un nuevo compilador escrito en Rust. Los benchmarks del propio Tailwind ponen los builds completos en torno a 5x más rápidos (de 378ms a 100ms en su medición) y los rebuilds incrementales que no compilan CSS nuevo más de 100x más rápidos, cayendo a microsegundos. Encima, una configuración “CSS-first” que elimina la necesidad de un archivo tailwind.config.js.

Las piezas que importan:

  • Nuevo motor en Rust: builds completos unas 5x más rápidos y rebuilds incrementales que terminan en microsegundos (100x+ más rápidos) gracias a la paralelización nativa.
  • Configuración en CSS: tailwind.config.js ya no es obligatorio. CSS-first es ahora el modo por defecto, con variables CSS nativas y la directiva @theme dentro de tu archivo CSS principal. Un config JS sigue funcionando si lo cargas de forma explícita con @config "./tailwind.config.js".
  • Integración nativa con Vite: se conecta directamente como plugin de Vite (@tailwindcss/vite), sin wrappers específicos del framework como @astrojs/tailwind.
  • Plugins en CSS: ahora se importan directamente en el CSS con la directiva @plugin.
  • Container queries en el core: lo que antes requería un plugin ahora viene integrado.

De 8.5 segundos a 890ms, el mismo proyecto

Antes de Tailwind v4, mi proyecto con ~500 componentes compilaba en 8.5 segundos. Con v4: 890ms.

Los benchmarks

MétricaTailwind v3Tailwind v4Mejora
Build inicial8.5s0.89s9.5x más rápido
Rebuild (HMR)420ms45ms9.3x más rápido
Tamaño del output12.4KB11.1KB10% más pequeño

¿Por qué tanto? Rust permite paralelización real. v4 procesa archivos en múltiples threads mientras v3 se queda en el único thread de Node.js, esperando.

tailwind.config.js ya no es obligatorio

El cambio más radical es que el archivo de configuración JavaScript ya no es obligatorio. CSS-first es el modo por defecto, y si aun así quieres un config JS lo cargas de forma explícita con @config "./tailwind.config.js". Ya no se detecta automáticamente.

Antes (v3)

// tailwind.config.js
module.exports = {
  theme: {
    extend: {
      colors: {
        primary: '#3b82f6',
        secondary: '#8b5cf6',
      },
      fontFamily: {
        sans: ['Inter', 'system-ui', 'sans-serif'],
      },
      spacing: {
        '128': '32rem',
      }
    }
  },
  plugins: [
    require('@tailwindcss/typography'),
    require('@tailwindcss/forms'),
  ],
}

Después (v4)

/* src/styles/global.css */
@import "tailwindcss";

@theme {
  /* Colores personalizados */
  --color-primary: #3b82f6;
  --color-secondary: #8b5cf6;

  /* Fuentes */
  --font-sans: 'Inter', system-ui, sans-serif;

  /* Spacing custom */
  --spacing-128: 32rem;

  /* Breakpoints */
  --breakpoint-3xl: 1920px;
}

/* Plugins ahora son CSS nativo */
@plugin "@tailwindcss/typography";
@plugin "@tailwindcss/forms";

Lo que ganas:

  1. Type-safety mejorado: las variables CSS tienen mejor autocomplete en VSCode.
  2. Hot reload instantáneo: los cambios en el theme se aplican sin recompilar.
  3. Menos abstracción: lo que ves en el CSS es lo que obtienes.

El setup correcto en Astro (y el que rompe)

Antes de nada: no uses @astrojs/tailwind con Tailwind v4. Causa conflictos.

Setup para Astro + Tailwind v4

npm install tailwindcss @tailwindcss/vite
// astro.config.mjs
import { defineConfig } from 'astro/config';
import tailwindcss from '@tailwindcss/vite';

export default defineConfig({
  vite: {
    plugins: [tailwindcss()],
  },
});
/* src/styles/global.css */
@import "tailwindcss";
---
// src/layouts/Layout.astro
import '@/styles/global.css';
---

Y la versión que rompe:

// MAL
import tailwind from '@astrojs/tailwind';

export default defineConfig({
  integrations: [tailwind()], // Esto rompe Tailwind v4
});

@apply dentro de @keyframes: el cambio que más duele

El cambio más doloroso de v4: @apply no funciona dentro de @keyframes.

El caso que falla

/* MAL: esto ROMPE en Tailwind v4 */
@keyframes fade-in {
  from {
    @apply opacity-0 scale-95;
  }
  to {
    @apply opacity-100 scale-100;
  }
}

Error:

@apply is not supported within at-rules like @keyframes

El arreglo: CSS vanilla

/* BIEN: usa CSS estándar en keyframes */
@keyframes fade-in {
  from {
    opacity: 0;
    scale: 0.95;
  }
  to {
    opacity: 1;
    scale: 1;
  }
}

/* Luego aplica la animación */
.fade-enter {
  animation: fade-in 0.3s ease-out;
}

La otra trampa: @apply con dark mode en Astro

<style>
  /* MAL: esto puede fallar en algunos casos */
  .card {
    @apply bg-white dark:bg-gray-900;
  }
</style>

Error:

The `dark:bg-gray-900` class does not exist

El arreglo son variables CSS más clases regulares:

<div class="card bg-white dark:bg-gray-900">
  <!-- contenido -->
</div>

<style>
  /* Solo estilos que no dependen de Tailwind */
  .card {
    border-radius: 0.5rem;
    box-shadow: 0 1px 3px rgba(0,0,0,0.1);
  }
</style>

Cinco problemas de migrar en serio

1. Plugins desactualizados

Muchos plugins de v3 directamente no funcionan en v4.

Solución temporal:

// Usa la versión v4-compatible
npm install @tailwindcss/typography @tailwindcss/forms

2. Custom utilities con addUtilities()

Antes (v3):

// tailwind.config.js
const plugin = require('tailwindcss/plugin');

module.exports = {
  plugins: [
    plugin(({ addUtilities }) => {
      addUtilities({
        '.scrollbar-hide': {
          '-ms-overflow-style': 'none',
          'scrollbar-width': 'none',
          '&::-webkit-scrollbar': {
            display: 'none'
          }
        }
      });
    })
  ]
}

Después (v4):

/* src/styles/global.css */
@layer utilities {
  .scrollbar-hide {
    -ms-overflow-style: none;
    scrollbar-width: none;

    &::-webkit-scrollbar {
      display: none;
    }
  }
}

3. Arbitrary values

v3:

<div class="w-[calc(100%-2rem)]">

v4: (Funciona igual, pero ahora con mejor autocompletion)

<div class="w-[calc(100%-2rem)]">

4. Sintaxis de opacidad en colores

v3:

<div class="bg-blue-500/50">

v4: (Sin cambios, pero ahora más eficiente)

<div class="bg-blue-500/50">

5. Container queries

Antes requería un plugin. Ahora viene integrado en v4:

<div class="@container">
  <div class="@md:grid-cols-2">
    <!-- Se adapta al contenedor, no al viewport -->
  </div>
</div>

Dark mode sin el flash

Tailwind v4 mejora el soporte para dark mode basado en clases.

La configuración que recomiendo

@import "tailwindcss";

@variant dark (&:where(.dark, .dark *));

Esto permite:

<!-- Opción 1: Clase en root -->
<html class="dark">
  <div class="bg-white dark:bg-gray-900">
</html>

<!-- Opción 2: Clase en contenedor específico -->
<div class="dark">
  <p class="text-gray-900 dark:text-white">
</div>

Evitar FOUC (Flash of Unstyled Content)

<script>
  // Ejecuta ANTES del render
  if (localStorage.theme === 'dark' ||
      (!localStorage.theme && window.matchMedia('(prefers-color-scheme: dark)').matches)) {
    document.documentElement.classList.add('dark');
  }
</script>

La herramienta de upgrade hace la mitad del trabajo

npx @tailwindcss/upgrade

La ejecutas y convierte tailwind.config.js a @theme en tu CSS, actualiza los imports en los archivos y detecta plugins incompatibles. Útil.

Lo que no hace: migrar custom utilities (eso te toca a ti) ni arreglar @apply dentro de keyframes (esos los reescribes a mano). Reserva tiempo para ambas cosas.

Trucos de producción que sobrevivieron a la migración

1. Purge agresivo (ya no hace falta)

En v3 tenías que configurar purge. En v4 el tree-shaking es automático y más inteligente. Una cosa menos.

2. Usar CSS nesting

/* Aprovecha el nesting nativo de CSS */
.card {
  @apply rounded-lg shadow-md;

  &:hover {
    @apply shadow-xl scale-105;
  }

  & .card-title {
    @apply text-xl font-bold;
  }
}

3. Optimizar fuentes con variables CSS

@theme {
  --font-sans: 'Inter var', system-ui, sans-serif;
  --font-mono: 'JetBrains Mono', monospace;
}

Luego usa font-variation-settings para weights dinámicos:

.dynamic-weight {
  font-variation-settings: 'wght' var(--font-weight);
}

Tres migraciones, tres facturas

Portfolio personal (20 páginas)

  • Tiempo de migración: 2 horas
  • Build time: 6.2s → 0.78s
  • Problemas encontrados: 3 custom plugins (reescritos a CSS)

Dashboard SaaS (150 componentes)

  • Tiempo de migración: 1 día
  • Build time: 14.5s → 1.2s
  • Problemas encontrados: @apply en keyframes (8 casos), plugin de animaciones custom

E-commerce (300+ componentes)

  • Tiempo de migración: 2 días
  • Build time: 28s → 2.1s
  • Problemas encontrados: conflicto con @astrojs/tailwind, 15 utilities custom migradas

¿Vale la pena migrar ahora?

Sí, si:

  • Tu build time es >5 segundos
  • Usas Vite/Astro (mejor integración)
  • Estás empezando un proyecto nuevo
  • Quieres aprovechar container queries nativas

Espera, si:

  • Tienes muchos plugins custom incompatibles
  • Tu equipo no puede dedicar 1-2 días a la migración
  • Dependes de @apply en keyframes intensivamente
  • Usas frameworks que aún no soportan v4 oficialmente

Tres meses en producción, y la factura salió a favor

El marcador después de 3 meses con v4:

  • Deploy times: reducidos en un 65%
  • Developer experience: HMR instantáneo (sin lag perceptible)
  • Bundle size: 10-15% más pequeño
  • Bugs: solo 2 edge cases con dark mode en Safari

Dos edge cases en Safari es una factura que pago encantado a cambio de un build de 890ms. La migración duele al principio. Luego tus rebuilds tardan 45ms y dejas de pensar en tu herramienta de build, que es el mejor cumplido que una herramienta de build puede ganarse.

Si tu build tarda más de 5 segundos, ya sabes qué hacer con tu próxima tarde libre.

Lectura relacionada: el setup de este post asume Astro. Si todavía estás decidiendo el framework, empieza por cómo Astro dejó este sitio en 18KB de JavaScript.


P.D. ¿Migrando un proyecto v3 y te has topado con algo que este post no cubre? Cuéntamelo en Twitter/X.

Comparte este artículo

Artículos relacionados