Skip to content

Last updated: First published:

ClientRouter or @view-transition? A Feature Comparison

Before you dive into a possible migration, you probably want to better understand what features you will lose and what benefits you can gain.

Switching from Astro’s <ClientRouter />1 to native cross-document view transitions offers a major advantage: you’re moving from a frame-work specific solution to a web standard. Astro always tried to keep that gap as small as possible. However, it’s important to consider the trade-offs. The <ClientRouter /> provides functionality that native cross-document view transitions lack. Some of these features will not be important to you, others need to be replaced with custom solutions or third-party tools. Some may not be achievable at all with full-load navigation.

Here’s a breakdown of the features you’ll gain and lose when migrating from Astro’s <ClientRouter /> to native cross-document view transitions:

#TopicLossGain
1Fallback SimulationSome simplified animation effects even on Firefox-
2Page LoadWhole page is available before transition startsDoes not have to wait for the page to be loaded before the transition starts
3Render FreezeMinimal by designMinimal with pre-render
4transition:persistKeeping elements and their state from page to page[has bit of a workaround]
5SPA StateShared state across pagesClean page load semantics
6Directional AnimationsBrowser back button slides the other way[has workaround]
7Reduced MotionAutomatically disables all animationsPlanned: automatically disables motion of default animations
8Event ListenersHook into preparation and swappingHook in directly before and after the page changes
9Source ElementKnow which element triggered the transitionOnly with Navigation API (e.g. not on Safari yet)
10Generated NamesWorks cross-document-
11Data AttributesControl link and script behavior[not needed]
12Route AnnouncementSupport for assistive technologyStandard support for assistive technology
13Programmatic Navigationnavigate()location.assign(), location.replace()
14Popstate hasUAVisualTransitionCancel view transitions if the browser uses its own transitions for traversals-

Some of these points might have a bigger or smaller impact than they initially seem. Let me explain in more detail:

Fallback Simulation

In the early days, when only Chrome supported view transitions (and even then, only behind a flag) providing a fallback for browsers without native support was a thing. Today, this primarily affects Firefox, but now its days without view transitions support hopefully are numbered. While not all users have the latest browser versions the importance of the fallback solution continues to diminish.

It’s worth noting that the View Transition API can not be polyfilled. The fallback simulation can mimic some effects, but it is not a true replacement. Depending on your needs, you might find the differences negligible or frustrating. If you used <ClientRouter /> with fallback="swap" or fallback="none", you will not miss any animations on Firefox.

Page Load

Browsers typically begin rendering a webpage’s content as soon as the first bytes are received. With browser-native cross-document view transitions, the browser attempts to ensure the content above the fold is ready before the transition starts. However, its heuristics aren’t perfect, and broken animations can occur. To avoid this, you should take control by specifying which parts of the page should load before rendering begins.

In contrast, Astro’s <ClientRouter /> initiates view transitions only after the new page has fully loaded. This approach avoids broken animations but delays content visibility, which can take a while for slower-loading pages.

Takeaway: With the <ClientRouter /> it can take longer until content is displayed but it avoids broken animations out of the box. Browser-native cross-document view transitions may need some tuning but also provide finer control.

Render Freeze

View transitions have a phase where the browser’s renderer is paused. This occurs between the moment the API captures snapshots of the old DOM and when the viewTransition.ready promise resolves. During this time, the browser executes the code that replaces the old DOM with the new DOM and processes lifecycle events.

Astro’s default actions are to tweak the attributes of the <html> element and the children of the old <head> to resemble those of the new DOM and to replace the body element. This is fast.

Browser-native cross-document view transitions, on the other hand, must establish an entirely new page context and have to at least load a initial part of the page. Browsers optimize for speed, up to the point of pre-loading and pre-rendering the new page if guided by speculation rules. However, some effort from the author’s side is required to make the pause as small as Astro’s.

transition:persist

Because the <ClientRouter/> technically turns a multi-page site into a single-page app, DOM elements can persist across navigations, moving from the old DOM to the new DOM while retaining their state and event handlers. This behavior is not replicable with full-page loads.

However, you can use tools like the Bag’s @vtbag/element-crossing to transfer HTML element properties across navigations. While it can’t replicate internal state and handlers, it enables additional features like continuing CSS animations across navigations. That would normally reset to the start state when using transition:persist.

SPA State

Sharing state between pages isn’t limited to transition:persist. With the <ClientRouter />, both your scripts and state managers like Nano can handle it. The window object, the <html> element, the <head> element, the module loader, and the global object are not reset during navigation.

This has its advantages: sharing state between pages is straightforward. But it also comes with a downside: since the browser state isn’t reset, one of the biggest challenges when using <ClientRouter /> on multi-page sites is properly reinitializing scripts after navigation.

In contrast, browser-native cross-document view transitions clear all script state during navigation. If you need to share state, you’ll have to persist it manually, such as by using sessionStorage. The bright side of this approach is that each page starts with a clean window object and a fresh module loader, following the standard behavior of traditional navigation.

Directional Animations

With <ClientRouter />, each view transition has a direction: forward or back. While this doesn’t affect default cross-fade animations, it’s crucial for slide animations. For example, when navigating forward, pages slide out to the left and in from the right. When navigating backward (e.g., using the browser’s back button), they slide out to right and in from the left.

The View Transition API supports view transition types that can be used to differentiate forward and backward animations. However, the View Transition API doesn’t automatically assign any types. With browser-native cross-document view transitions, slides will always originate from the same direction, regardless of whether you’re navigating forward or backward.

But don’t worry, the Bag has your back!

Reduced Motion

When using <ClientRouter /> all view transition animations are disabled if the browser’s prefer-reduced-motion setting is enabled. This value is typically inherited from a similar setting on the operating system.

Disabling motion (though not necessarily cross-fade effects) for users who prefer it is a good practice. Currently, the View Transition API does not handle this automatically. However, from discussions I expect that the API will soon respect prefers-reduced-motion by disabling the default morph animation from the dynamic user-agent stylesheet. For custom animations, though, it’s up to you to honor the user’s preferences. We’ll explore how to do this later.

Event Listeners

With the View Transition API, two new events where introduced: pageswap and pagereveal. Additionally, the Navigation API provides the navigate event.

In comparison, the <ClientRouter /> offers five events: astro:before-preparation, astro:after-preparation, astro:before-swap, astro:after-swap, and astro:page-load.

The *-preparation events are somewhat comparable to the navigate event, while the *-swap events align with the page* events. The astro:page-load is similar to the standard load event. However, there is no one-to-one correspondence between those groups of events.

Source Element

With the <ClientRouter /> the sourceElement property of astro:before-preparation and astro:before-swap events points to the anchor or form that was clicked to initiated the view transition. This allows you to create logic based on what triggered the transition.

The NavigationEvent of the Navigation API also provides this property. However, not all browsers that support cross-document view transitions have implemented the Navigation API yet. Looking at you, Safari 👀.

Generated Names

You can set view transition names for the <ClientRouter /> using transition:name. If you don’t set them, Astro automatically invents some for you. It won’t tell you the names, but ensures that they are unique on each page. If you use the same structure on different pages, e.g. using a global Layout, chances are good that matching elements on two pages are assigned the same random name, giving you morph animations on corresponding elements.

The View Transition API also supports auto-assigned view transition names:

view-transition-name: auto;

These are also random and unique on each page. Sadly, they are even unique across all pages. This means elements from the old page never automatically match those on the new page, resulting in exit and entry animations rather than morph animations.

As we’ll see later, you can either retain Astro’s names or address this limitation using JavaScript.

Data Attributes

When you set an data-astro-reload attribute on a link, you opt in for a full page load instead of Astro’s view transitions. As browser-native cross-document view transitions always do a full page reload, you won’t miss that one.

When you set an data-astro-rerun attribute on a script, the client router will execute it no matter if it was on the last page or not. As browser-native cross-document view transitions always execute all scripts during load, you won’t miss that one.

Route Announcement

The <ClientRouter /> has built-in support assistive technology to announce the page’s title on soft navigation. With the full page loads of browser-native cross-document view transitions this is the standard behavior and does not need special treatment anymore.

Programmatic Navigation

The <ClientRouter /> provides the navigate() function to navigate to another page with view transitions. With @view-transition, you can call location.assign() or location.replace() to navigate to another page with browser-native cross-document view transitions.

Popstate hasUAVisualTransition

If you want, you can add the following incantation to your Astro site:

Layout.astro
<script>
addEventListener('popstate', (e) => e.hasUAVisualTransition && location.reload());
</script>

When a browser, like iOS Safari, has its own visual transition as you swipe the screen back and forth, it would look bad to follow that with additional view transitions. The line above cancels the client routers view transition in that case. Try it right now on this site!

Popstate does not fire on browser-native cross-document view transitions, and so there is currently no similar detection for hasUAVisualTransition when migrating away from the <ClientRouter/>.

Footnotes

  1. Interpret “<ClientRouter />” as “<ClientRouter /> or <ViewTransitions />”, depending on your Astro version.