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.
El <ClientRouter /> nos regaló una navegación fluida, pero a costa de un detalle que hasta ahora dimos por bueno: cada salto sigue intercambiando el <body> entero, y con él destruye todo lo que vivía dentro —el vídeo que se estaba reproduciendo, el contador de una isla de React, el texto a medio escribir en un formulario—. La transición se ve suave, pero por debajo el mundo se reinicia. transition:persist es la respuesta a ese problema: marca un elemento como superviviente, y Astro, en lugar de recrearlo en la página nueva, arranca el nodo vivo de la página vieja y lo trasplanta entero, con su DOM, su estado y su reloj interno intactos. Es la diferencia entre reconstruir algo idéntico y llevarse lo mismo.
- Entender por qué el intercambio de DOM reinicia por defecto todo el estado de la página.
- Marcar con
transition:persistun elemento para que sobreviva intacto a la navegación. - Persistir medios en reproducción, islas de framework con estado y formularios a medio rellenar.
- Emparejar elementos con
transition:persistnombrado y controlar props contransition: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 persistente
import 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ó.
La clave para razonar sobre transition:persist es recordar dónde reside cada cosa. El HTML describe la estructura inicial; el estado vivo —posición de un vídeo, valor de un campo, memoria de una isla— reside en el nodo del DOM y en el JavaScript ligado a él. Recrear el nodo desde el HTML reproduce la estructura pero pierde el estado. Persistir el nodo conserva ambos, porque no lo copia: lo lleva. Esa distinción entre estructura y estado vivo explica de un vistazo qué necesita persistir y qué no.
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.
flowchart TD A[navegacion con ClientRouter] --> B[el nodo esta marcado como persistente] B -->|no| C[se descarta y se recrea desde el HTML nuevo] C --> D[estado perdido audio detenido isla remontada] B -->|si| E[se arranca el nodo vivo de la pagina vieja] E --> F[se trasplanta intacto al DOM nuevo] F --> G[estado conservado audio sigue isla intacta] style A fill:#89b4fa,color:#11111b style D fill:#f38ba8,color:#11111b style G fill:#a6e3a1,color:#11111b
Medios que no se cortan
Un audio o video con transition:persist sigue reproduciendose a traves de la navegacion, sin reiniciarse.
Islas con memoria
Una isla de framework persistida conserva su estado en vez de remontarse y perderlo en cada salto.
Formularios intactos
El texto tecleado sobrevive porque el valor vive en el nodo; persistir el nodo conserva lo escrito.
Nombre y props
transition:persist con nombre empareja aunque cambie de sitio; persist-props congela la isla e ignora props nuevas.
Detrás de transition:persist hay una pregunta vieja y honda disfrazada de detalle técnico: ¿qué hace que algo siga siendo lo mismo a lo largo del tiempo? El intercambio de DOM por defecto responde de una manera ingenua: dos elementos son el mismo si tienen el mismo HTML. Bajo esa lógica, un vídeo recreado con idéntico marcado es el vídeo anterior —y sin embargo cualquiera que lo estuviera viendo sabe que no, porque se detuvo, porque perdió su minuto veintitrés, porque su continuidad se rompió—. transition:persist adopta la respuesta contraria, la que coincide con la intuición humana: la identidad de una cosa no está en que su forma coincida, sino en que su historia no se interrumpa. Un reproductor es el mismo no porque su HTML sea igual, sino porque nunca dejó de sonar; una isla es la misma porque su estado nunca se borró; un formulario es el mismo porque conserva lo que le confiaste. Astro implementa esta idea de la manera más literal posible: no clona el nodo para fingir continuidad, arranca el original y se lo lleva, garantizando que lo que persiste no es una copia convincente sino la cosa misma. Y aquí está la enseñanza que trasciende la directiva: en una interfaz, romper la continuidad de aquello con lo que el usuario tiene una relación viva —su música, su vídeo, sus palabras a medio escribir— es una forma de traición, por muy fluida que se vea la animación que la disfraza. Persistir no es una optimización; es respetar que, para quien está al otro lado, ciertas cosas no deben morir solo porque cambió la URL.
- Coloca un
<audio controls>contransition:persisten el layout, ponlo a sonar y navega entre páginas: comprueba que no se corta. - Añade una isla contadora con
client:loadytransition:persist, incrementa el contador, navega y verifica que el valor sobrevive. - Pon un
<input>en el layout contransition:persist, escribe algo, cambia de sección y confirma que el texto sigue ahí. - Elimina el elemento persistente de una de las páginas destino y observa cómo, al faltar su pareja, el elemento desaparece en ese salto.