Overview

<ClientRouter /> turns an Astro site into a client-routed one: it intercepts link clicks, fetches the next page, swaps the DOM, and animates the change with the browser’s View Transition API where available. Add it to the base layout and navigations get a cross-fade with no framework runtime. Astro 5 renamed <ViewTransitions /> to <ClientRouter />, and Astro 6 removed the old name, so ViewTransitions imports fail the build.

Add <ClientRouter /> to the base layout head

---
import { ClientRouter } from "astro:transitions";
---
<html>
  <head><ClientRouter /></head>
  <body><slot /></body>
</html>

Pair elements across pages with transition:name

Give the same logical element the same transition:name on both pages to animate it from one position to the other. A name must be unique on a page, so namespace it with a slug or ID.

<img src={post.cover} transition:name={`cover-${post.slug}`} />

Choose an animation with transition:animate

The built-in animations are fade (the default), slide, initial (the browser’s default styling), and none. There is no morph. Pass options to fade and slide from astro:transitions.

---
import { slide } from "astro:transitions";
---
<main transition:animate={slide({ duration: "0.3s" })}><slot /></main>

Keep state across navigations with transition:persist

transition:persist keeps an element, and for an island its state, instead of replacing it. Use it for a media player or a client-state island that must survive navigation; without it, navigating away unmounts the island and discards its state. transition:persist-props also stops the persisted island from receiving new props.

<AudioPlayer client:load transition:persist />

Know the fallback modes and browser support

Same-document view transitions work in Chrome and Edge 111+, Safari 18, and Firefox 144. Elsewhere the router still navigates in the browser using the fallback prop: animate (the default, which simulates the animation), swap (immediate DOM replacement), or none (a normal full-page load). Pages work without animation, so treat it as progressive enhancement.

<ClientRouter fallback="swap" />

Re-run scripts on every navigation

Bundled module scripts run once and do not run again after a transition. Put setup code in an astro:page-load listener, which fires after each navigation and on first load. Use astro:after-swap to restore theme or state right after the DOM swap, and data-astro-rerun to re-run an inline script every time. The lifecycle events are astro:before-preparation, astro:after-preparation, astro:before-swap, astro:after-swap, and astro:page-load.

<script>
  document.addEventListener("astro:page-load", () => { /* initialize widgets */ });
</script>

Astro 7 removed the TRANSITION_* constants and helper functions from astro:transitions; compare event.type with the event name strings instead.

Opt out where a client-side swap is wrong

Add data-astro-reload to a link or form for a full page load, for example an OAuth redirect or payment confirmation. data-astro-history sets push, replace, or auto. The router announces each navigation to assistive technology and disables animations when prefers-reduced-motion is set.

<a href="/checkout/confirm" data-astro-reload>Confirm payment</a>