Skip to content

View Transitions

A traditional multi-page site does a full browser reload on every navigation — reliable, but the screen flashes white and scroll position resets. A SPA avoids that but pays for it with a heavy JavaScript router. Astro’s view transitions give you the smooth feel of a SPA on top of a normal MPA, for a few kilobytes of script.

You opt in by adding one component, <ClientRouter />, to your page <head> — usually in a shared layout so it applies site-wide:

---
import { ClientRouter } from 'astro:transitions';
---
<html lang="en">
<head>
<title>My site</title>
<ClientRouter />
</head>
<body>
<slot />
</body>
</html>

With that in place, navigations between pages on your site are intercepted and handled on the client: Astro fetches the next page, swaps in its content, and animates the change — no full reload. Browsers that don’t support the underlying API simply fall back to a normal navigation, so nothing breaks.

flowchart LR
  click["User clicks a link"] --> intercept["ClientRouter intercepts (no full reload)"]
  intercept --> fetch["Fetch the next page HTML"]
  fetch --> swap["Swap in the new content, animate"]
  swap --> event["Fire astro:page-load"]
ClientRouter swaps content instead of reloading

By default Astro cross-fades the page. You control animations per element with directives:

  • transition:name — give an element the same name on both pages so Astro morphs one into the other (a shared-element transition, like a thumbnail growing into a hero image).
  • transition:animate — choose the animation (fade, slide, or a custom one). Import helpers from astro:transitions.
  • transition:persist — keep a component’s DOM and state across the navigation instead of re-rendering it. Perfect for a playing video or an audio player that shouldn’t restart.
---
import { slide } from 'astro:transitions';
---
<!-- This heading slides between pages; the player keeps playing across nav. -->
<h1 transition:name="page-title" transition:animate={slide({ duration: 200 })}>Title</h1>
<video controls autoplay transition:name="player" transition:persist />

Because navigation no longer triggers a full page load, scripts that ran on DOMContentLoaded won’t re-run on client-side navigations. Astro fires its own events so you can re-initialize:

  • astro:page-load — fires after every navigation (initial load and client-side swaps). Put initialization here instead of DOMContentLoaded.
  • astro:after-swap — fires right after the new DOM is swapped in, before it paints (good for restoring theme/state to avoid a flash).
document.addEventListener('astro:page-load', () => {
// runs on first load AND after every client-side navigation
initAnalytics();
});

This is the most common view-transitions gotcha: an init script that works on first load but “stops working” after navigating, because it was bound to DOMContentLoaded. Move it to astro:page-load.

How do you enable view transitions in Astro?
What is the current name of the component (older Astro called it something else)?
What does `transition:persist` do?
An init script bound to `DOMContentLoaded` stops working after a client-side navigation. What is the fix?