View Transitions: Persisting Arbitrary Client‑Side State Across Navigations
0 reputation · 02 Feb 2026, 08:34 UTC
0 reputation · 02 Feb 2026, 08:34 UTC
In a site built with Astro’s ClientRouter (formerly ViewTransitions), I need to keep the scroll position of nested containers and arbitrary JavaScript state that lives outside of islands alive when the user navigates between pages using the client‑side router.
Astro’s transition:persist attribute only preserves DOM elements that match by ID or class across the two pages; it does not automatically restore scroll offsets or non‑island state. The astro:page-load lifecycle event can re‑hydrate islands, but there is no documented hook for restoring generic state or scroll positions. Third‑party scripts that assume a full page load also need manual re‑initialization, which can lead to memory‑leak‑like symptoms if left dangling.
34775 reputation · 02 Feb 2026, 17:09 UTC
Use sessionStorage to save the values you need before the view‑transition swap and restore them after the new page is mounted, hooking into Astro’s documented navigation events.
transition:persist only preserves DOM elements that match by ID or class; it does not persist scroll offsets or non‑island state.astro:before-swap, astro:after-swap and astro:page-load (the latter runs after islands are re‑hydrated). There is no built‑in hook for generic JavaScript state or scroll positions.sessionStorage survives a client‑side navigation and is cleared when the tab closes.Because the framework does not automatically save scroll positions or arbitrary state, you must manually capture that information just before the DOM is replaced and re‑apply it after the new page has been prepared. This avoids duplicate listeners and memory‑leak‑like symptoms from third‑party scripts.
scrollTop of a nested container and any ad‑hoc JavaScript values.astro:before-swap (fires before the old DOM is removed).function saveState() {
const container = document.querySelector('#nested-scroll');
if (container) {
sessionStorage.setItem('nestedScrollTop', container.scrollTop.toString());
}
// Example of arbitrary state
sessionStorage.setItem('appState', JSON.stringify({ foo: 'bar' }));
}
window.addEventListener('astro:before-swap', saveState);
astro:page-load (fires after islands are re‑hydrated).function restoreState() {
const top = sessionStorage.getItem('nestedScrollTop');
if (top !== null) {
const container = document.querySelector('#nested-scroll');
if (container) {
container.scrollTop = parseInt(top, 10);
}
}
const raw = sessionStorage.getItem('appState');
if (raw !== null) {
try {
const state = JSON.parse(raw);
// re‑apply your ad‑hoc logic here
} catch (_) { /* ignore corrupted data */ }
}
}
window.addEventListener('astro:page-load', restoreState);
init() and teardown() pair. Call teardown() in the astro:before-swap handler and init() in the astro:page-load handler to avoid duplicate listeners or timers.Do the nested containers you wish to preserve have stable IDs or class names that remain unchanged across the source and destination pages? If they do, you could also rely on transition:persist for the container element itself, but the scroll offset would still need the manual restore shown above.
Use comments to ask for clarification. Post a solution as an answer.
No question comments on this page.