🏠 Home Page
Welcome to the Astro Component Swap demo! This demonstrates how to preserve component state during view transitions.
Try This:
- Increment the counter below
- Navigate to the Example page
- Come back to this page
- Notice the counter preserved its value! ✨
Persistent Counter
This counter maintains its state during page navigation!
Try incrementing the counter, then navigate to other pages. The counter value should persist thanks to component swap!
The Problem
When navigating between pages in Astro with view transitions, framework components are destroyed and recreated. Astro statically builds ands renders them server-side with their initial values, then hydrates them client-side.
To preserve their state, we use Astro's "transition:persist" directive. But this requires to have the component on every page the user might navigate to keep its state (both Javascript and DOM wise) alive.
This is not solved by having their state saved external to the component, like using Nanostores. Because there will be a brief flash between the initial, un-hydrated state of the HTML document and the code processed by the hydratation process. Other workarounds were to listen to ClientRouter events to hide the components until they have been properly mounted with the data we need, resulting in bad UX and possibly even more flashes of blank space across the page.
The Solution
Our component swap utility preserves component state by moving DOM elements to a persistent hidden container with id="client-swap-container" located at the bottom of the body during transitions. Then, if these components are found again (by checking their "data-swap-id"), they will get swapped back before the page renders. This eliminates the hydration flash completely and preserves their DOM and Javascript states without any other workaround, resulting in a seamless user and developer experience. No need to use external stores either. The component preserves the entire state on its own.
Tested with Svelte 5. We need tests with other frameworks but I can't see why it wouldn't work. We need to be sure nonetheless.
To Improve
This is a "hack" around Astro's ClientRouter events. In a potential official Astro implementation, we should look for a syntax similar to "transition:" directives and auto-generation of the wrapper in the body where the components get moved to.
There could be edge cases that I'm not aware of. One of them is what if the component tagged with this directive gets used in a different page from the one it's been actually saved? I would like to guess that, as long as they are declared with the same id, similar to how "transition:" directives work, they will get swapped across pages and preserved as well in pages where they are not present. This might be even desired. Otherwise, if no "id" has been provided, it should target the element from the original page and position. Maybe by generating unique random "ids" at build time. I think "transition:" directives already do that.
Performance
I believe this is as performant as it can get. We're just moving DOM elements across the document, and Javascript states gets preserved in the browser at all times. I can't think of any other way that not only would be a chore for the developer, but also prone to more bugs and possibly worse performance in the context of the browser.