I am using Astro.js as a container in microfrontend architecture. Each page download microfrontend (runtime) and inject css and js into the <head>. The problem is, that I am using code splitting. The chunks are injecting dynamic css files into the <head>. Whenever I go to the another route and back with full site view transitions (SPA mode), the <links> are missing. I was experimenting with transition:persist, but it is not suitable for this case. What options do I have?
#Preserve dynamic <links> and <scripts> in <HEAD> while using full site view transitions
21 messages · Page 1 of 1 (latest)
Could you make a minimal reproduction to demonstrate this issue?
CC @crude talon maybe you have some ideas as to what is going on? Mr ViewTransition Wizard
Hi @umbral lark 👋 The ClientRouter works with the static HTML files. When you navigate to another route, it loads that DOM and changes the current DOM to reflect the new one. Then it runs the new scripts. If those scripts also manipulate the DOM results may be other than expected. As Adam said, having a minimal reproduction will really help a lot to understand how to find a solution for your case.
Thank you for mentioning!
I made the minimal reproduction:
https://stackblitz.com/edit/github-3put1i6h?file=src%2Fpages%2Findex.astro,public%2Fapp-5acc59bc.js,src%2Flayout%2Flayout.astro
You can see, that the microfrontend application is rendered, but the styles are missing
I’ll take look…
@umbral lark, mounting the app works fine when you revisit / after /page. The reason for the CSS not being re-inserted into the head on mount is buried somewhere in that 90k minified js. I assume, it contains internal state that 2knows" that the stylesheet is still there and does not know that the ClientRouter removed it as it was not in the loaded DOM.
Maybe you can override Astro's swap function, disabling the part that removes stylesheets. But this makes only sense if the script would remove them when they are not needed, which i doubt.
Because the problem here arises from shared state across pages, i would replace <ClientRouter /> with
<style>@view-transition{navigation: auto}</style> and use browser native view transitions where supported.
If your issue is resolved, please help by doing the following two steps:
- From the ellipses (3-dot menu) in the top-right corner of the post (not the first message), edit the tags to include the Solved tag.
- From the same ellipses, select Close Post.
Your post will still be available to search and can be re-opened simply by replying in it. Closing a post moves it down with older posts, so we can more easily focus on issues that still need to be resolved.
Thank you for your help!
I would like to prefer SPA mode because of microfrontends optimization. I would rather clean styles whenever they are not needed. How can I override the swap function?
Finally I solved it with astro:before-swap as you suggest:
<script>
import { swapFunctions } from 'astro:transitions/client';
function swapMainOnly(doc) {
console.error('swapping')
swapFunctions.deselectScripts(doc);
swapFunctions.swapRootAttributes(doc);
// swapFunctions.swapHeadElements(doc);
const restoreFocusFunction = swapFunctions.saveFocus();
const newMain = doc.querySelector('main');
const oldMain = document.querySelector('main');
if (newMain && oldMain) {
swapFunctions.swapBodyElement(newMain, oldMain);
} else {
swapFunctions.swapBodyElement(doc.body, document.body);
}
restoreFocusFunction();
}
document.addEventListener('astro:before-swap', (e) => {
e.swap = () => swapMainOnly(e.newDocument);
});
</script>
Not ideal because even title is not replaced. But I will create some internalSwapHeahElemets function.
Thank you very much for help!
Sorry for just coming late to the party. Yes, if the script takes care for the header, that is an excellent solution. As you said, tweaking the head swap for the title shouldn't be too hard. Happy you found that solution and the docs link. and thank you for documenting it here for others to follow 😉 👍
This is the default implementation of swapHeadElements. https://github.com/withastro/astro/blob/d8e3c65f8db1148f2072baeb4ec399fc3cdcb9cd/packages/astro/src/transitions/swap-functions.ts#L48-L66
Perhaps just exempting the relevant stylesheet <link>s would work
.... and you should consider to set data-astro-rerun on your mount script
https://docs.astro.build/de/guides/view-transitions/#data-astro-rerun
I was optimizing the solution and the best approach is probably push the links to the new document I would like to exchange:
<script>
import { swapFunctions } from 'astro:transitions/client';
document.addEventListener('astro:before-swap', (e) => {
e.swap = () => swap(e.newDocument);
});
function swap(doc) {
swapFunctions.deselectScripts(doc);
swapFunctions.swapRootAttributes(doc);
document.querySelectorAll('link[rel="stylesheet"]').forEach((link) => {
doc.head.append(link)
});
swapFunctions.swapHeadElements(doc);
const restoreFocusFunction = swapFunctions.saveFocus();
swapFunctions.swapBodyElement(doc.body, document.body);
restoreFocusFunction();
}
</script>
With that, I will keep using swapHeadElements together with persistedHeadElement directly from astro without mirroring the functionality to my codebase
I even simplifed this to:
<script>
document.addEventListener('astro:before-swap', (e) => {
document.querySelectorAll('link[rel="stylesheet"]').forEach((link) => {
e.newDocument.head.append(link)
});
});
</script>
Perfect!

It is most fun to see how a solution just gets better and better even if I am always too late to really support 😳
You were a really big help! I would not notice in transition view documentation that I can use swap functions. Again, thak you very much!