Saltar al contenido

Los eventos del ciclo: reaccionar a cada navegación

La navegación con el ClientRouter no es un salto opaco, sino un ciclo con etapas que emite eventos sobre document. Qué significan astro:before-preparation, astro:after-swap y astro:page-load; dónde encaja astro:before-swap; el problema clásico de que los scripts de módulo no se re-ejecutan al intercambiar el DOM, y cómo astro:page-load es el gancho para re-inicializar todo lo que debe correr en cada página.

La navegación como un ciclo con etapas

Cuando pulsas un enlace con el router activo, no ocurre un único evento sino una cadena. Primero Astro prepara: decide cargar la página destino y trae su HTML. Luego intercambia: reemplaza el DOM actual por el nuevo dentro de la view transition. Por último la deja cargada: la transición termina y la página está lista para vivir. Cada frontera entre esas etapas emite un evento sobre document, de modo que puedes intervenir antes de preparar, justo antes o después del intercambio, o cuando todo ha terminado.

TypeScript
document.addEventListener('astro:before-preparation', (event) => {  // arranca la navegacion: aun no se ha cargado la pagina destino});document.addEventListener('astro:after-swap', () => {  // el DOM nuevo ya esta, pero la transicion aun no ha terminado});document.addEventListener('astro:page-load', () => {  // la pagina esta cargada y lista; tambien corre en la carga inicial});

before-preparation: el arranque de la navegación

astro:before-preparation es el primer eslabón: se dispara al iniciarse la navegación, antes de que la página destino se haya cargado. El objeto del evento trae información útil —de dónde vienes, a dónde vas, la dirección del salto y el tipo de navegación— y expone el cargador que traerá la página, que puedes incluso reemplazar para casos avanzados. Su uso más honesto y frecuente es mostrar un indicador de carga: como aquí sabes que empieza un viaje que puede tardar, es el momento de encender una barra de progreso, que apagarás cuando la página esté lista.

TypeScript
document.addEventListener('astro:before-preparation', () => {  document.body.classList.add('cargando');});document.addEventListener('astro:page-load', () => {  document.body.classList.remove('cargando');});

Ese indicador se apoya en la simetría del ciclo —enciendes al principio, apagas al final—, pero el objeto del evento ofrece más que un simple disparador: describe el viaje. De él puedes leer de dónde vienes, a dónde vas y, sobre todo, la dirección del salto —hacia delante o hacia atrás—, un dato que permite adaptar la interfaz al sentido de la navegación.

TypeScript
document.addEventListener('astro:before-preparation', (event) => {  // 'forward' al avanzar, 'back' al pulsar atras  document.documentElement.dataset.direccion = event.direction;});

Con ese dato escrito en un atributo del documento, tu CSS puede animar distinto al avanzar que al retroceder, replicando a mano la sensibilidad direccional que slide trae de fábrica. El evento no solo te avisa de que algo empieza: te cuenta qué clase de movimiento es, y esa información es materia prima para transiciones que se sienten coherentes con el gesto del usuario.

after-swap y before-swap: el instante del trasplante

Entre la preparación y el final está el momento crítico: el intercambio del DOM. Dos eventos lo rodean. astro:before-swap se dispara justo antes, cuando la página nueva ya está construida pero todavía no ha sustituido a la vieja; su objeto de evento da acceso al documento entrante y permite incluso personalizar cómo se hace el intercambio. astro:after-swap se dispara justo después, cuando el DOM nuevo ya está en su sitio pero la transición aún no ha concluido.

Ese astro:after-swap tiene un uso muy concreto y valioso: fijar estado que debe estar puesto antes de que el usuario vea la página, para evitar un parpadeo. El caso de libro es el tema claro/oscuro. Si lees la preferencia del usuario y aplicas la clase en astro:page-load, corres el riesgo de que la página se pinte un instante con el tema por defecto y luego cambie. Haciéndolo en astro:after-swap, la clase ya está antes del primer pintado.

TypeScript
document.addEventListener('astro:after-swap', () => {  const tema = localStorage.getItem('tema');  if (tema === 'oscuro') {    document.documentElement.classList.add('oscuro');  }});

Su gemelo astro:before-swap es más avanzado y menos habitual, pero abre una puerta que conviene conocer. Como recibe el documento entrante antes de que sustituya al actual, permite inspeccionarlo y retocarlo —trasladar al nuevo <html> un atributo del viejo, conservar una clase de estado que se perdería en el intercambio— e incluso reemplazar por completo la mecánica del trasplante a través de un método que el propio evento expone. Es el punto donde el desarrollador que necesita controlar el intercambio hasta el último detalle toma las riendas. La mayoría de las veces no harás nada aquí y astro:after-swap bastará; pero saber que before-swap existe te da una salida cuando el comportamiento por defecto, por una vez, no es el que necesitas.

El problema de los scripts que dejan de correr

Aquí está el desconcierto que mencionábamos. En Astro, un <script> normal es un módulo, y los módulos tienen una regla del lenguaje: se ejecutan una sola vez, la primera vez que se cargan. En la web multipágina clásica eso no se notaba, porque cada recarga era una carga nueva y el script volvía a correr. Pero con el <ClientRouter /> no hay recarga: se intercambia el DOM. El módulo ya se ejecutó una vez y no se vuelve a ejecutar aunque su <script> aparezca en la página nueva. Por eso el código que inicializa algo —un carrusel, un menú, un listener— funciona en la primera carga y misteriosamente deja de funcionar al navegar.

La solución no es un truco, es un cambio de mentalidad: el código que debe correr en cada página no puede confiar en la ejecución única del módulo; tiene que engancharse a astro:page-load, que se dispara en cada navegación —y también en la carga inicial—. Ese doble disparo es deliberado y perfecto: te permite escribir la inicialización una vez y que corra siempre, tanto al entrar por primera vez como en cada salto posterior.

TypeScript
function inicializarMenu() {  const boton = document.querySelector('.menu-boton');  boton?.addEventListener('click', () => {    document.querySelector('.menu')?.classList.toggle('abierto');  });}// se ejecuta en la carga inicial y en cada navegacion posteriordocument.addEventListener('astro:page-load', inicializarMenu);

Puestos en orden, los cuatro eventos cuentan la historia completa de una navegación: astro:before-preparation al arrancar, astro:before-swap cuando la página nueva está lista para entrar, astro:after-swap en cuanto ha entrado, y astro:page-load cuando todo ha terminado. Entre medias, Astro emite también astro:after-preparation al acabar de cargar el destino, de modo que la secuencia es en realidad una escalera fina de peldaños que puedes pisar según la precisión que necesites. Dominar en qué punto de esa línea temporal corre tu código es dominar las view transitions a fondo: deja de haber magia, y en su lugar hay una secuencia clara en la que sabes exactamente dónde colgar cada cosa —el indicador al arranque, el estado sin parpadeo antes del pintado, la inicialización al final—.