transition:persist: conservar islas y estado entre páginas
La directiva que le dice a Astro que no destruya un elemento al navegar, sino que lo lleve intacto a la página siguiente. Por qué el intercambio de DOM reinicia todo por defecto, cómo persistir un reproductor de audio o vídeo en marcha, una isla de framework con su estado o un formulario a medio rellenar, cómo emparejar elementos con transition:persist con nombre, y el matiz de transition:persist-props.
Por qué, por defecto, todo se reinicia
Recordemos el mecanismo. Cuando navegas con el <ClientRouter />, Astro trae el HTML de la página destino y reemplaza el <body> actual por el nuevo. Ese reemplazo es total: los nodos viejos se descartan y se crean otros a partir del HTML entrante. Para el contenido estático eso es inofensivo —un párrafo recreado es idéntico al original—, pero para cualquier cosa con estado vivo es una pequeña catástrofe. Un <audio> que sonaba se detiene porque el elemento que lo reproducía ya no existe; una isla interactiva vuelve a montarse desde cero y pierde lo que el usuario había cambiado; los campos de un formulario recuperan sus valores iniciales.
La razón es que el estado no está en el HTML, sino en el nodo del DOM y en la memoria asociada a él. Recrear el nodo produce algo que se parece al original pero no es el original: un gemelo recién nacido sin memoria. Mientras la navegación fue una recarga, esto era inevitable y nadie lo cuestionaba. Ahora que la navegación es un intercambio controlado, Astro puede hacer algo mejor: exceptuar ciertos nodos del reemplazo.
transition:persist: marcar un superviviente
La directiva transition:persist es esa excepción. Al ponerla en un elemento, le dices a Astro que, en lugar de sustituirlo por su equivalente de la página nueva, conserve el nodo actual y lo mueva tal cual al DOM entrante. El elemento no se recrea: es literalmente el mismo objeto, con el mismo estado, la misma posición de reproducción, los mismos valores. El caso canónico es un reproductor multimedia dentro del layout que debe seguir sonando mientras el usuario navega.
---// src/layouts/Base.astro---<html lang="es"> <head> <ClientRouter /> </head> <body> <slot /> <footer> <audio src="/podcast.mp3" controls transition:persist></audio> </footer> </body></html>Con ese transition:persist, el audio no se corta al cambiar de página: el nodo sobrevive al intercambio y sigue reproduciéndose sin un salto perceptible. Lo mismo vale para un <video>, para un mapa interactivo o para cualquier elemento cuyo estado interno sería caro o molesto reconstruir.
Islas con estado y formularios a medio rellenar
La persistencia brilla especialmente con las islas de framework. Una isla de React, Vue o Svelte mantiene su estado en memoria; sin persistir, cada navegación la vuelve a montar y ese estado se evapora. Con transition:persist, la isla viaja entera —su componente sigue montado, sus hooks vivos, su estado intacto— de una página a otra.
---// un contador de React como isla persistenteimport Contador from '../components/Contador.jsx';---<Contador client:load transition:persist />Conviene distinguir dos clases de estado que la persistencia protege, porque no son lo mismo aunque se salven igual. Una isla de framework guarda estado de JavaScript —variables en memoria, hooks, subscripciones vivas— que solo sobrevive si el componente no se desmonta; persistir su nodo evita ese desmontaje, y con él la evaporación de todo lo que vivía en memoria. Un elemento nativo, en cambio, guarda estado del DOM —el valor de un campo, el minuto de un vídeo, el desplazamiento de un contenedor—, que también se perdería al recrear el nodo pero no depende de ningún framework. transition:persist cubre las dos clases con el mismo gesto, porque ambas residen justo donde ella actúa: en el nodo que decide no destruir.
El mismo principio salva el trabajo del usuario en formularios. Imagina una barra de búsqueda o un formulario largo en el layout: sin persistir, navegar a otra sección borra lo tecleado. Marcado con transition:persist, el nodo del formulario sobrevive y conserva cada carácter que el usuario había escrito, porque el valor de un campo vive en el nodo, no en el HTML.
<form transition:persist> <input name="q" placeholder="Buscar..." /></form>Ese mismo mecanismo conserva algo que a menudo se olvida: el desplazamiento interno de un contenedor. Una barra lateral larga por la que el usuario ha hecho scroll, un panel de navegación con decenas de enlaces, una lista con posición propia —todos vuelven al principio cuando el nodo se recrea, y todos la mantienen si el nodo persiste—. En sitios de documentación, donde el índice lateral acompaña al lector página tras página, persistir ese contenedor es la diferencia entre un índice que salta al inicio en cada salto y uno que se queda exactamente donde el lector lo dejó.
Emparejar por nombre y el matiz de las props
Por defecto, transition:persist empareja el elemento por su posición en el árbol: espera encontrar en la página nueva un elemento equivalente en el mismo lugar. Pero a veces el mismo componente vive en sitios distintos de dos páginas, o quieres asegurar el emparejamiento sin depender de la posición. Para eso, transition:persist acepta un nombre: dos elementos con el mismo nombre persistente se identifican como el mismo aunque no coincidan sus posiciones.
<aside transition:persist="barra-lateral"> <!-- se conserva aunque cambie de sitio entre paginas --></aside>Queda un matiz importante con las islas. Cuando persistes una isla, Astro conserva su estado pero, por defecto, actualiza sus props con los valores que trae la página nueva —lo cual puede provocar que la isla se vuelva a renderizar con datos distintos—. Si quieres que la isla ignore las props nuevas y siga exactamente como estaba, añade transition:persist-props. Es la diferencia entre “sigue viva pero acepta datos frescos” y “sigue viva y congelada tal cual”.
<!-- conserva estado Y props: la isla queda idéntica, sin re-render --><Reproductor client:load transition:persist transition:persist-props />Una condición gobierna todo esto: el elemento persistente debe existir en ambas páginas. Si la página destino no contiene un elemento que empareje —por posición o por nombre—, no hay a qué trasplantarlo y el elemento simplemente desaparece. Por eso la persistencia encaja de forma natural en el layout compartido, donde el reproductor, la barra o el formulario están presentes en todas las rutas por construcción.
Merece una nota la relación entre persistir y animar, porque son estrategias opuestas que conviene no confundir. Un elemento con transition:persist no participa de la view transition como los demás: no se captura una versión vieja y una nueva para fundirlas, porque no hay dos versiones —es el mismo nodo, que simplemente permanece—. Por eso un elemento persistido no parpadea ni se desliza al navegar: se queda quieto y estable mientras el resto de la página transiciona a su alrededor. Esa quietud es justo lo que quieres para un reproductor o una barra fija, y es incompatible por naturaleza con emparejarlo por transition:name para que morfe. Conservar el nodo y reemplazarlo animando son caminos que se excluyen: cada elemento toma uno.
Conviene, por último, no abusar. Persistir tiene un coste conceptual: un elemento que sobrevive a la navegación deja de reflejar por fuerza la página en la que estás, y sin criterio puedes acabar arrastrando estado que ya no viene a cuento —una barra lateral que muestra el índice de otra sección, campos que pertenecían a la vista anterior—. La regla sana es persistir solo aquello con lo que el usuario mantiene una relación viva en ese instante: lo que suena, lo que escribe, lo que mira de verdad. Todo lo demás gana más de lo que pierde al recrearse fresco en cada salto, porque así queda siempre en sintonía con la página que lo contiene.