Elegí Astro para varios sitios precisamente porque sirve HTML y manda muy poco JavaScript al cliente. La contrapartida clásica de ese modelo es que cada clic recarga la página entera: parpadeo blanco, scroll que salta, se pierde el estado. Las View Transitions de Astro me dieron la parte buena de una SPA —navegación fluida, sin recargas visibles, con estado que persiste— sin volver a arrastrar un router de cliente y megas de bundle. Este artículo es cómo las uso y, sobre todo, dónde están los bordes que conviene conocer antes de activarlas.
Qué problema resuelven de verdad
En un sitio MPA tradicional cada navegación es una recarga completa del documento. Funciona, es robusto y es lo que Astro hace de serie, pero se siente tosco: entre página y página hay un instante en blanco, el navegador reinicia el scroll y cualquier cosa que estuviera sonando o reproduciéndose se corta. En una SPA eso no pasa porque nunca recargas: un router intercepta el clic y reemplaza el contenido con JavaScript. El precio es que te llevas todo el aparato de la SPA —el router, la hidratación, el estado en cliente— aunque tu sitio sea mayormente contenido.
Las View Transitions se colocan en medio. Astro intercepta la navegación, pide la siguiente página, intercambia el contenido del <body> sin recargar el documento y anima la transición usando la API de View Transitions del navegador. Sigues sirviendo páginas HTML normales, pero la navegación entre ellas deja de parpadear. Activarlo es una línea en el layout.
---
// src/layouts/Layout.astro
import { ClientRouter } from 'astro:transitions';
---
<html lang="es">
<head>
<ClientRouter />
</head>
<body>
<slot />
</body>
</html>
Con ese <ClientRouter /> en el <head>, cada enlace interno pasa a navegarse sin recarga y con una transición suave por defecto. No has escrito un router ni has convertido nada en SPA: sigue siendo Astro sirviendo HTML.
Animaciones con nombre: el detalle que lo vende
La transición por defecto es un fundido discreto, pero lo que engancha es poder animar elementos concretos de una página a otra. Si marco dos elementos en páginas distintas con el mismo transition:name, el navegador entiende que son "el mismo" y anima el cambio de posición y tamaño entre ambos. El caso típico es la miniatura de un artículo que crece hasta convertirse en la cabecera del detalle.
<!-- En el listado -->
<img src={post.cover} transition:name={`cover-${post.slug}`} />
<!-- En la página de detalle -->
<img src={post.cover} transition:name={`cover-${post.slug}`} />
El nombre tiene que ser único por elemento en cada página; por eso lo derivo del slug. Si repites un mismo transition:name para varios elementos visibles a la vez, el navegador no sabe cuál es cuál y la animación se rompe. Es el error más común al empezar.
El estado que quieres que sobreviva
Aquí está la ganancia menos obvia. Como no hay recarga completa, puedo pedir que ciertos elementos persistan entre navegaciones en lugar de recrearse. Un reproductor de audio, un mapa pesado, un menú con su estado abierto: con transition:persist el elemento se conserva tal cual al pasar de página, sin reiniciarse.
<audio src={cancion} controls transition:persist />
Sin esto, cada navegación destruiría el <audio> y volvería a crearlo, cortando la reproducción. Con transition:persist, el mismo nodo del DOM viaja a la página siguiente y sigue sonando. Es exactamente el tipo de continuidad por la que la gente monta una SPA entera, y aquí lo consigo con un atributo.
💡 Cada elemento persistido es un nodo que decides no recrear. Úsalo para lo que debe tener continuidad real —audio, vídeo, un mapa caro— y no como parche para no recalcular algo que sí debería refrescarse con la nueva página.
Los bordes que hay que conocer
Nada de esto es gratis, y es honesto decir dónde aprietan. El primero es el scripting. Como la página no recarga, el <script> que corría en DOMContentLoaded no se vuelve a ejecutar en cada navegación: se ejecutó una vez y la nueva página no lo dispara de nuevo. Cualquier inicialización que dieras por hecha en cada carga hay que reengancharla al evento astro:page-load, que Astro emite en la carga inicial y después de cada transición.
// Se ejecuta en la primera carga y tras cada navegación
document.addEventListener('astro:page-load', () => {
inicializarWidgets();
});
Este es el fallo que más cuesta detectar, porque el sitio funciona perfecto al recargar a mano y solo se rompe al navegar entre páginas: los scripts simplemente no se reengancharon.
El segundo borde es la degradación. Las View Transitions se apoyan en una API que no todos los navegadores implementan igual; donde no está, Astro cae con elegancia a una navegación normal con recarga. Eso significa que no puedes tratar la transición como garantizada: es una mejora progresiva, no una base sobre la que construir lógica. Y el tercero, más de criterio que técnico, es no pasarse: animarlo todo marea. Reservo las animaciones con nombre para una o dos conexiones visuales que de verdad aporten continuidad, y dejo el resto en el fundido discreto.
Por qué me sale a cuenta
Elegí View Transitions en Astro en vez de saltar a una SPA porque me quedo con lo que quería del framework —HTML servido, JavaScript mínimo, páginas independientes que cachean y se indexan bien— y encima gano la fluidez que antes obligaba a cambiar de modelo entero. A cambio acepto un puñado de bordes concretos: reenganchar scripts en astro:page-load, tratar la transición como progresiva y ser disciplinado con las animaciones. Es un intercambio que casi siempre acepto, porque el coste es acotado y conocido, mientras que meterme en una SPA por conseguir una animación sería pagar en bundle, complejidad y SEO un precio muy superior al problema que resuelve.