Actualizar a Astro 7: el compilador en Rust ya no te perdona el HTML sucio
Tabla de Contenidos
Astro 7 salió el 22 de junio de 2026 y es la actualización más profunda del framework desde que existe: el compilador de .astro está reescrito en Rust, el pipeline de Markdown ya no usa remark/rehype, y el motor de render pasa de recursivo a encolado.
Para la mayoría de proyectos el upgrade es de unos minutos. Pero hay tres cambios que rompen en silencio —y uno de ellos, el de espacios en blanco, puede pegarte palabras en producción sin que ningún error te avise.
Esta guía la escribo desde este mismo sitio: ortamarco.me corre Astro 7.0.7 en producción, con Vite 8, MDX, plugins rehype propios y adaptador de Node. Los números y las trampas que verás abajo salen de aquí, no de las notas de la versión.
Si lo que buscas es una introducción a Astro —qué es, islas, Actions, Content Layer—, eso está en la guía completa de Astro.js. Aquí vamos a la migración.
Qué cambió, en una pantalla
| Área | Antes (Astro 6) | Ahora (Astro 7) |
|---|---|---|
Compilador .astro | Go | Rust, y es el único |
| Markdown / MDX | unified (remark + rehype) | Sätteri (Rust, sobre pulldown-cmark y Oxc) |
| Render | Recursivo | Encolado, ~2.4× más rápido |
| Bundler | Vite 7 | Vite 8 (Rolldown) |
compressHTML | true | 'jsx' |
| Routing | Convencional | Advanced Routing con src/fetch.ts |
| Caché | Por adaptador | Astro.cache + routeRules |
@astrojs/db | Existía | Eliminado |
Requisito mínimo: Node 22.12.0. Si vienes de Node 20, ese salto es obligatorio y además Node 20 murió en abril de 2026, así que toca de todas formas.
Los números, y qué se puede creer de ellos
El anuncio oficial habla de builds entre un 15 % y un 61 % más rápidos, con algunos sitios construyendo más del doble de rápido. Los benchmarks publicados son concretos:
| Sitio | Astro 6 | Astro 7 |
|---|---|---|
| astro.build | 62.70 s | 24.24 s |
| docs.astro.build | 114.54 s | 73.53 s |
| tauri.app | 86.12 s | 55.33 s |
| developers.cloudflare.com | 386.89 s | 261.94 s |
| biomejs.dev | 176.39 s | 149.90 s |
| aspire.dev | 385.84 s | 326.11 s |
Fíjate en el rango: astro.build baja un 61 %, pero biomejs.dev solo un 15 %. La mejora depende muchísimo de cuánto Markdown tengas, porque el grueso viene de Sätteri, no del compilador. El propio equipo mide que el compilador Rust por sí solo aporta «aproximadamente un 6 %» en docs.astro.build.
Mis números medidos en este sitio, ahora mismo:
Astro 7.0.7 · Node 24.16.0 · WSL2
102 posts · 163 páginas HTML · 125 MB de salida
Build en frío (sin dist ni .astro): 17.69 s (servidor: 14.06 s)
Build en caliente: 17.36 s (servidor: 13.79 s)
Índice de Pagefind: 0.23 s (102 páginas, 15 368 palabras)
Aviso honesto: no puedo darte el delta. Este sitio migró a 7 sin dejar una medición limpia de la 6, así que compararlo sería inventármelo. Lo que sí te sirve del dato de arriba es la escala: un sitio de cien y pico páginas con MDX, Preact, Tailwind 4 y dos plugins rehype se construye entero en menos de 18 segundos. Si el tuyo está en ese orden de magnitud y tarda mucho más, el problema no es Astro.
Trampa 1: el compilador en Rust ya no arregla tu HTML
El compilador de Go corregía en silencio el HTML mal formado: cerraba etiquetas que dejabas abiertas y reordenaba anidamientos inválidos. El de Rust no. Trata el marcado tal cual.
Esto se traduce en tres cosas:
Las etiquetas sin cerrar ahora son error de compilación. No un warning: el build falla.
<!-- Astro 6: se cerraba solo, y tú ni te enterabas -->
<div class="card">
<p>Hola
<!-- Astro 7: error de compilación -->
Los elementos void (<br>, <img>, <input>, <hr>) siguen sin necesitar cierre. Y los atributos sin terminar también revientan ahora.
El HTML inválido ya no se reordena. Si tienes un <div> dentro de un <p>, antes el compilador lo sacaba fuera silenciosamente para producir HTML válido. Ahora lo deja donde lo pusiste, y el navegador aplicará sus reglas de recuperación — que no son las mismas. El resultado es que una página puede renderizarse distinto sin que nada falle.
Este es, con diferencia, el caso más traicionero de todo el upgrade, porque no hay error que perseguir. Si tienes componentes antiguos con anidamiento dudoso, compáralos renderizados antes y después.
La serialización de CSS cambia. Los valores de color y el formato de url() pueden salir escritos de otra forma. Es puramente cosmético y no afecta al comportamiento, pero si tienes tests de snapshot sobre CSS, van a fallar en masa por nada.
La buena noticia: los dos primeros casos se cazan compilando. El tercero se caza leyendo el diff.
Trampa 2: compressHTML pasa de true a 'jsx'
Esta es la que muerde de verdad, y la que quiero que te lleves aunque no leas nada más.
El nuevo valor por defecto aplica las reglas de espacios en blanco de JSX: los saltos de línea entre elementos inline dejan de producir un espacio visible.
<span>hola</span>
<em>mundo</em>
- Astro 6 renderizaba
hola mundo - Astro 7 renderiza
holamundo
En prosa y en enlaces esto es un desastre silencioso. Ningún error, ningún warning: simplemente tu texto empieza a salir con palabras pegadas, y lo descubres cuando alguien te lo dice.
Tienes dos salidas. La correcta a largo plazo es adoptar las reglas de JSX y poner los espacios explícitos:
<span>hola</span>{" "}
<em>mundo</em>
La pragmática, y la que yo elegí en este sitio, es fijar el valor antiguo y migrar después de forma deliberada:
// astro.config.mjs
export default defineConfig({
// Astro 7 cambió el default de `compressHTML` de `true` a `'jsx'` (elimina
// espacios en blanco entre elementos inline como React). Lo fijamos en `true`
// para conservar exactamente el output de v6 y evitar que se peguen palabras
// en prosa/enlaces. Se puede migrar a `'jsx'` más adelante de forma deliberada.
compressHTML: true,
});
Ese comentario está copiado literalmente de la configuración de este blog. Fijarlo convierte un problema de contenido —que se descubre tarde y mal— en una tarea de refactor que haces cuando quieras.
Si decides adoptar 'jsx', la forma seria de hacerlo es construir dos veces y comparar el HTML servido, no ir página por página a ojo:
# Construye con el valor antiguo, guarda una página representativa
npm run build && cp dist/client/blog/tu-post/index.html /tmp/antes.html
# Cambia a compressHTML: 'jsx', reconstruye
npm run build && cp dist/client/blog/tu-post/index.html /tmp/despues.html
Y ahí pegas los dos y miras qué se movió:
Trampa 3: Sätteri sustituye a remark y rehype
Astro 7 reemplaza el pipeline de unified por Sätteri, un procesador escrito en Rust sobre pulldown-cmark y Oxc. En builds de documentación grandes esto quita más de un minuto, y trae soporte nativo para GFM, puntuación tipográfica, IDs de encabezado, contenedores, matemáticas y frontmatter — cosas que antes pedían un plugin cada una.
El precio es que @astrojs/markdown-remark ya no se instala por defecto. Si dependes de plugins remark o rehype, la documentación te pide instalarlo y declarar markdown.processor: unified(), o portar tus plugins al formato MDAST/HAST de Sätteri.
Aquí va un matiz que verifiqué en este repo y que no he visto documentado en ningún sitio. Este blog usa dos plugins rehype propios:
// astro.config.mjs
markdown: {
syntaxHighlight: {
type: 'prism',
excludeLangs: ['mermaid'],
},
rehypePlugins: [rehypeResponsiveTables, rehypeMermaid],
},
No declara markdown.processor: unified() y funciona igual: los diagramas mermaid se renderizan y las tablas salen con su contenedor responsive. El motivo es que @astrojs/markdown-remark entra igualmente como dependencia transitiva:
$ npm ls @astrojs/markdown-remark
[email protected]
├─┬ @astrojs/[email protected]
│ └── @astrojs/[email protected]
└─┬ [email protected]
└── @astrojs/[email protected] deduped
Traducido: si usas @astrojs/mdx, el paquete llega solo y tus plugins rehype siguen corriendo. Aun así, yo lo declararía explícitamente en package.json en vez de depender de una transitiva, porque el día que Astro deje de arrastrarlo tu build se rompe sin que hayas tocado nada.
Verifica el tuyo antes de asumir nada:
npm ls @astrojs/markdown-remark
Y comprueba que los plugins realmente se ejecutan mirando el HTML construido, no el dev server:
grep -c "lo-que-tu-plugin-inyecta" dist/client/ruta/index.html
Flags experimentales que hay que quitar
Cinco funciones salieron de experimental. Si las tenías activadas, hay que sacarlas del bloque experimental o el build se queja:
| Flag experimental | Qué hacer ahora |
|---|---|
rustCompiler | Quitar. Es el compilador por defecto y único |
queuedRendering | Quitar. Activo por defecto |
advancedRouting | Quitar. Activo por defecto |
logger | Mover al campo logger de nivel superior |
cache y routeRules | Mover fuera de experimental, a nivel superior |
src/fetch.ts es ahora un nombre reservado
Con Advanced Routing, src/fetch.ts pasa a ser un archivo reservado para configurar el pipeline de peticiones. Si ya tenías un archivo con ese nombre para otra cosa, tienes tres opciones:
// Apuntar a otro archivo
export default defineConfig({ fetchFile: './src/router.ts' });
// O desactivar Advanced Routing por completo
export default defineConfig({ fetchFile: null });
O simplemente renombrar tu archivo. Es un choque poco probable, pero cuando ocurre el síntoma es desconcertante.
Lo que se eliminó
@astrojs/db desaparece por completo y queda sin mantenimiento. Las alternativas son node:sqlite (integrado en Node, sin compilación nativa), Drizzle ORM, o lo que uses ya. Este sitio usa node:sqlite directamente para reseñas, contadores y leads, y la experiencia es buena: cero dependencias nativas y WAL activado a mano.
Las APIs internas de astro:transitions ya no se exportan. Fuera TRANSITION_BEFORE_PREPARATION, TRANSITION_AFTER_PREPARATION, TRANSITION_BEFORE_SWAP, TRANSITION_AFTER_SWAP, TRANSITION_PAGE_LOAD, y las funciones isTransitionBeforePreparationEvent(), isTransitionBeforeSwapEvent() y createAnimationScope().
Se sustituyen por los nombres de evento directos, que además siempre fueron más legibles:
document.addEventListener('astro:before-preparation', (e) => { /* ... */ });
document.addEventListener('astro:after-swap', (e) => { /* ... */ });
getContainerRenderer() cambia de ruta de import. Ya no sale de la raíz del paquete, sino de un entrypoint dedicado:
// Antes
import { getContainerRenderer } from '@astrojs/react';
// Ahora
import { getContainerRenderer } from '@astrojs/react/container-renderer';
Aplica igual a @astrojs/preact, @astrojs/solid-js, @astrojs/svelte, @astrojs/vue y @astrojs/mdx.
Lo nuevo que sí vas a querer
Superadas las trampas, hay tres cosas que justifican el upgrade por sí solas.
Advanced Routing con src/fetch.ts. Control total del pipeline de peticiones usando el patrón estándar de handler fetch que ya usan Cloudflare Workers, Deno y Bun. Sirve para interceptar peticiones, reenviarlas a un backend, o componer Astro como middleware junto a algo como Hono.
Caché de rutas con Astro.cache y routeRules. Una API agnóstica de plataforma con semántica HTTP e invalidación por etiquetas:
// Declarativo, por grupo de rutas, fuera del código de la ruta
export default defineConfig({
routeRules: {
'/blog/**': { cache: { maxAge: 3600, tags: ['blog'] } },
},
});
Los proveedores de CDN para Netlify, Vercel y Cloudflare están todavía en experimental.
Render encolado, ~2.4× más rápido. Sustituye el enfoque recursivo anterior. No hay nada que hacer: viene activado y se nota en sitios con muchos componentes anidados.
El upgrade, paso a paso
# 1. Rama nueva
git checkout -b upgrade/astro-7
# 2. Node 22.12 o superior (24 LTS es la apuesta sensata)
node -v
# 3. La actualización asistida de Astro
npx @astrojs/upgrade
# 4. Quita los flags experimentales que ya no existen
# rustCompiler, queuedRendering, advancedRouting
# y mueve logger, cache y routeRules a nivel superior
# 5. Fija compressHTML antes de construir, para aislar problemas
# compressHTML: true en astro.config.mjs
# 6. Construye. Aquí salen las etiquetas sin cerrar
npm run build
# 7. Comprueba que tus plugins de markdown siguen vivos
npm ls @astrojs/markdown-remark
El paso 5 es deliberado: si actualizas y cambias el tratamiento de espacios a la vez, cuando algo se vea raro no vas a saber si fue el compilador o el whitespace. Fija true, deja el build en verde, y después decides si migras a 'jsx'.
Un apunte sobre la salida final: si ya venías comprimiendo el HTML por tu cuenta con alguna herramienta externa, revísalo — ahora hay dos capas haciendo lo mismo con reglas distintas.
Checklist antes de desplegar
- Node 22.12 o superior en local, CI y producción
-
experimentalsinrustCompiler,queuedRenderingniadvancedRouting -
logger,cacheyrouteRulesmovidos a nivel superior -
compressHTMLfijado explícitamente, no heredado - Build en verde: no quedan etiquetas ni atributos sin cerrar
-
npm ls @astrojs/markdown-remarkconfirma que está, si usas plugins - Plugins remark/rehype verificados sobre el HTML construido
- Ningún
src/fetch.tspropio con otro propósito - Imports de
getContainerRendererapuntando a/container-renderer - Sin restos de
@astrojs/dbni de las constantes internas deastro:transitions - Páginas con anidamiento HTML dudoso comparadas antes y después
Conclusión
Astro 7 es un upgrade que merece la pena y que en la mayoría de proyectos se resuelve en una tarde. Lo nuevo —Vite 8, Sätteri, render encolado, Advanced Routing, caché de rutas— llega sin pedir una reescritura.
Pero conviene entender de dónde vienen los problemas. Los dos cambios que rompen fuerte —el compilador estricto y compressHTML— no son bugs: son el framework dejando de tapar cosas que antes tapaba. El HTML mal cerrado siempre estuvo mal; simplemente ahora te enteras. Y los espacios entre elementos inline siempre fueron ambiguos; ahora hay una regla explícita.
La única que puede escaparse a producción sin avisar es la de los espacios. Fija compressHTML: true el primer día, y ya migrarás cuando tengas tiempo de leer el diff con calma.