Documentación técnicaAstro v4 → v7Tailwind v4WordPress headless240 páginas estáticas

De Astro 4 a Astro 7:
migración real y fantasma de rendimiento.

Documentación de la migración de laparadadelbus.com de Astro v4.0.0 a v7.0.3 realizada por Cris Culebras, sin downtime, en una copia local separada del proyecto en producción. Y lo que vino después, cuando los números de rendimiento empezaron a no cuadrar entre sí.

01. Aislar el entorno

El motivo de la migración no fueron las features nuevas. Fue un npm audit rutinario que reveló 13 vulnerabilidades de severidad alta en Astro v4: XSS reflejado, bypass de autenticación por doble URL-encoding, SSRF vía header Host, lectura arbitraria de archivos en el servidor de desarrollo. Todas resueltas en versiones posteriores.

Astro v4 a v7 implica tres saltos mayores. La recomendación general es no saltar de golpe en producción. La solución: clonar el proyecto a una carpeta de pruebas completamente aislada y validar cada paso antes de tocar el sitio real.

Astro v6 dejó de soportar Node 18 y 20. El mínimo requerido es Node 22.12.0. Si tu entorno de build local o CI no cumple el mínimo, esto se resuelve antes de tocar una sola línea de código.

terminal
# Clonar en entorno aislado
git clone <repo> laparadadelbus-astro7-test
cd laparadadelbus-astro7-test
node --version  # mínimo 22.12.0 requerido por Astro v6+

02. Limpiar vulnerabilidades

El audit mezclaba problemas de Astro con una vulnerabilidad moderada en js-yaml, dependencia transitiva de gray-matter. Separar ambos frentes evita confundir causas.

terminal
# Resuelve js-yaml sin breaking changes
npm audit fix

# Gestiona el salto de Astro y sus integraciones oficiales
npx @astrojs/upgrade
@astrojs/upgrade detecta automáticamente las versiones compatibles del ecosistema. En este caso también marcó @astrojs/tailwind para actualizar de v5 a v6, que fue el siguiente obstáculo.

03. Migrar Tailwind a v4

La integración oficial de Tailwind para Astro fue deprecada a partir de Tailwind v4. El intento de instalar @astrojs/tailwind@6.0.2 junto a astro@7.0.3 falla con un error de peer dependency. El camino recomendado ahora es el plugin nativo de Vite.

Tailwind v4 además mueve la configuración del tema desde tailwind.config.mjs a un bloque @theme directamente en CSS. Esto genera las mismas clases de utilidad que ya existían sin tocar ninguna línea de las plantillas .astro ya escritas.

terminal
npm uninstall @astrojs/tailwind tailwindcss
npm install tailwindcss@latest @tailwindcss/vite@latest
npm install astro@7.0.3
astro.config.mjs
import tailwindcss from '@tailwindcss/vite';

export default defineConfig({
  vite: {
    plugins: [tailwindcss()],
  },
});
global.css
@import "tailwindcss";

@theme {
  --color-lpdb-accent: #B8440E;
  --font-headline: 'Syne', sans-serif;
}

04. Errores de compilación

Error 1: @apply encadenado

Tailwind v4 no resuelve cadenas de @apply sobre clases propias definidas en el mismo archivo. La solución es desenrollar las utilidades explícitamente en cada clase derivada.

Error — Cannot apply unknown utility class
/* No funciona en Tailwind v4 */
.tag-pill { @apply inline-block px-2 py-0.5 text-xs uppercase; }
.tag-accent { @apply tag-pill bg-lpdb-accent text-white; }

/* Solución: desenrollar explícitamente */
.tag-accent {
  @apply inline-block px-2 py-0.5 text-xs uppercase bg-lpdb-accent text-white;
}

Error 2: ternarios anidados en template literals

El compilador reescrito en Rust es más estricto analizando expresiones complejas dentro de template literals JSX. Los ternarios anidados dentro de class fallan sin línea exacta en el stack trace. La solución: calcular la clase en una variable antes del JSX.

Señal de alerta: si el build falla con Unexpected token sin línea concreta, busca ternarios anidados en atributos class construidos con template literals.

05. Build y validación

240 páginas, 0 errores de compilación. El compilador de Astro v7 está reescrito en Rust y es notablemente más estricto con HTML inválido: tags sin cerrar que antes se autocorregían en silencio ahora producen error en build time.

Verificación antes de tocar producción: preview local con npm run preview, revisión visual de la home y fichas de contenido, comparación de clases reescritas contra el sitio en vivo, y consola del navegador limpia.

Lighthouse en preview local solo sirve como referencia orientativa. Las métricas de caché y compresión solo son fiables en producción, medidas con herramientas externas.

06. El fantasma de rendimiento

Con la migración ya en producción, el mismo sitio, sin tocar una línea de código entre mediciones, daba puntuaciones de Lighthouse que oscilaban entre 61 y 97 en ejecuciones consecutivas desde Chrome DevTools local.

La pista que rompió el misterio: tres herramientas independientes daban resultados altos y consistentes entre sí. Solo Lighthouse ejecutado desde la máquina local mostraba esa varianza salvaje.

100/100

CWV Insights (campo real)

97

PageSpeed Insights

Grado A

GTmetrix

La varianza no estaba en el código ni en el servidor. Estaba en la ruta de red entre el punto de medición local y el datacenter en cada momento concreto. Lighthouse local mete la conexión de quien mide como variable no controlada. Es la herramienta menos fiable de las cuatro para sacar conclusiones definitivas sobre el rendimiento real de un sitio.

07. Las mejoras que sí importaron

Antes de llegar a esa conclusión, hubo dos cambios de código con impacto real y medible en el desglose de LCP, independientemente de la varianza de red.

Mejora 1: defer en scripts inline del hero

Un script de búsqueda de ciudades, sin defer, competía por el hilo principal justo en la zona del documento donde vive la imagen de mayor contenido (LCP). Añadir defer bajó el retraso de renderizado de 1980ms a 1070ms sin tocar nada más.

Mejora 2: quemar el filtro CSS en la imagen

La imagen del hero llevaba grayscale contrast-125 como clases de Tailwind, es decir, filter: grayscale() contrast() aplicado por CSS. El navegador tiene que decodificar la imagen y luego componer ese filtro antes de poder pintarla, un coste que se paga en cada carga para cada visitante.

Procesando el efecto directamente en el archivo .webp con Photoshop y sirviendo la imagen ya con el aspecto final, ese coste de composición desapareció. El retraso de renderizado bajó de 1070ms a 50ms.

Lección generalizable: cualquier filter CSS sobre el elemento candidato a LCP (grayscale, blur, contrast, drop-shadow) tiene un coste de composición que se paga en cada visita. Si el efecto es estático, casi siempre es más barato quemarlo en el archivo de imagen.

Preguntas frecuentes

¿Se puede saltar directamente de Astro 4 a Astro 7?+

Técnicamente sí, pero no es recomendable hacerlo directamente en producción. Astro v4 a v7 implica tres saltos mayores (v4→v5→v6→v7). La forma segura es clonar el proyecto en una carpeta de pruebas aislada, actualizar con npx @astrojs/upgrade y validar cada paso antes de tocar el sitio real.

¿Por qué @astrojs/tailwind está deprecado en Astro 7?+

La integración oficial de Tailwind para Astro fue deprecada a partir de Tailwind v4. El camino recomendado ahora es el plugin nativo de Vite: npm install tailwindcss@latest @tailwindcss/vite@latest. La configuración del tema se mueve de tailwind.config.mjs a un bloque @theme en CSS.

¿Por qué Lighthouse local da resultados tan variables?+

La varianza no estaba en el código ni en el servidor. Estaba en la ruta de red entre el punto de medición local y el datacenter en cada momento concreto. Tres herramientas independientes (CWV Insights, PageSpeed Insights y GTmetrix) daban resultados altos y consistentes. Lighthouse local mete la conexión de quien mide como variable no controlada, lo que lo convierte en la herramienta menos fiable para sacar conclusiones definitivas.

¿Qué versión mínima de Node.js requiere Astro 7?+

Astro v6 dejó de soportar Node 18 y 20. El mínimo requerido es Node 22.12.0. Si tu entorno de build local o CI no cumple el mínimo, hay que actualizarlo antes de tocar una sola línea de código.

¿Qué es el error Cannot apply unknown utility class en Tailwind v4?+

Tailwind v4 no resuelve cadenas de @apply sobre clases propias definidas en el mismo archivo. Si tienes .clase-a con @apply de utilidades y .clase-b con @apply clase-a, la segunda falla. La solución es desenrollar las utilidades explícitamente en cada clase derivada en lugar de encadenar @apply sobre clases custom.

¿Cuál es el impacto real de migrar de Astro 4 a Astro 7 en producción?+

En laparadadelbus.com (240 páginas estáticas, WordPress headless), la migración realizada por Cris Culebras resultó en 0 errores de compilación, 0 downtime y mejoras verificables en LCP: el retraso de renderizado bajó de 1980ms a 50ms combinando defer en scripts y quemado de filtros CSS en la imagen hero. El build además es notablemente más rápido gracias al compilador reescrito en Rust.

Recursos relacionados

¿Necesitas migrar tu proyecto a Astro 7?

Si tienes un proyecto en Astro 4 o 5 y no quieres gestionar la migración tú mismo, Cris Culebras puede hacerlo con zero downtime y validación completa de rendimiento antes de tocar producción.