Rike Pool

When browsers grew up

I still remember the flicker. You click a link, the screen goes white, and you wait. That harsh refresh was the tax we paid for the simplicity of static HTML. It broke the user’s flow (and their focus).

For the longest time, our only escape was the Single Page Application. We built complex JavaScript architectures just to keep the header from blinking. We traded the simplicity of the server for the complexity of the client.

We do not have to make that trade anymore.

With the release of Firefox 144 last month, the View Transitions API primitives are finally supported across all major browser engines. This is a pivotal moment. It allows us to build Multi-Page Applications (MPAs) that feel indistinguishable from SPAs, but without the heavy JavaScript payload.

I have updated this site to use it. We can finally have our cake and eat it too.


The problem with the hard refresh

The traditional web navigation model is abrupt. The browser destroys the current document (CSS, DOM, script state) and paints a blank canvas before rendering the next page. This “hard refresh” is jarring. It disconnects the user from their spatial context.

SPAs solved this by hijacking the browser’s navigation event. They prevent the default reload, fetch data via an API, and swap pixels manually. This works, but it introduces significant overhead. You suddenly have to manage client-side routing, memory leaks, and focus management.

The View Transitions API moves this responsibility from user-land JavaScript to the browser engine itself.

How it works

The browser handles the heavy lifting. When a transition is triggered, the browser captures a screenshot of the current state. It then loads the new state (or page) and captures a screenshot of that. Finally, it cross-fades between the two snapshots while morphing shared elements.

Your job is simply to tell the browser which elements are the same using the view-transition-name property.

/* Old page: product list */
.thumbnail {
  view-transition-name: product-image;
}

/* New page: product detail */
.hero-image {
  view-transition-name: product-image;
}

https://developer.mozilla.org/en-US/docs/Web/CSS/view-transition-name

When the browser sees two elements with the same tag, it calculates the geometry differences and animates the pixel transformation. It handles position, size, and aspect ratio changes on the compositor thread. This ensures 60fps performance even on lower-end devices.

The ecosystem reality (November 2025)

There is a nuance here that matters. While Chrome and Safari support fully declarative, CSS-only cross-document transitions, Firefox 144 currently supports the JavaScript startViewTransition primitive (Level 1).

This means that for a pure HTML solution without any JavaScript, we are not quite at 100% cross-browser parity. However, because the JavaScript primitive is now baseline, tools can bridge the gap perfectly.

You can verify support in your console with this check:

if ('startViewTransition' in document) {
  console.log('View Transitions API is supported.');
}

https://developer.mozilla.org/en-US/docs/Web/API/View_Transitions_API

The Astro implementation

This is where Astro 5.0 shines. It wraps the platform primitives into a drop-in solution. It uses the native CSS API where available and falls back to the JavaScript API (now supported in Firefox 144) to orchestrate the swap.

Astro 5.0 renamed the old <ViewTransitions /> component to <ClientRouter /> to better reflect its role. It is not just animating; it is intercepting clicks to enable that app-like persistence.

---
// layouts/Layout.astro
import { ClientRouter } from 'astro:transitions';
---

<html>
  <head>
    <ClientRouter />
  </head>
  <body>
    <slot />
  </body>
</html>

https://docs.astro.build/en/guides/upgrade-to/v5/#renamed-viewtransitions—component

By adding this component to your head, you opt into a hybrid routing model. The browser loads your HTML, but subsequent navigations are intercepted. Astro swaps the DOM content and triggers the native View Transition.

Things to watch out for

The API is powerful, but you can easily abuse it.

  1. Accessibility is non-negotiable. Motion can trigger vestibular disorders. You must respect the prefers-reduced-motion media query.
  2. Performance. Do not animate the body tag. Morphing the entire viewport is expensive. Stick to distinct focal points like headers, images, or persistent sidebars.
  3. Browser Versioning. Firefox 144 was released on October 14, 2025. Ensure your user base has updated before relying on this for critical UI flows.

Here is the correct way to handle reduced motion preferences:

@media (prefers-reduced-motion: reduce) {
  ::view-transition-group(*),
  ::view-transition-old(*),
  ::view-transition-new(*) {
    animation: none !important;
  }
}

https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-reduced-motion


My take

We spent a decade breaking the web’s native navigation model to make it feel “premium.” We are now reversing that trend.

This aligns with my philosophy of “softening the stack.” We are letting the browser engine do what it was designed to do. We can ship simple HTML files again. We can delete thousands of lines of client-side router code.

I think we will look back at 2025 as the year the “app-like” feel became a standard HTML feature rather than a React dependency.


Common questions

Do I need a framework to use View Transitions?

No. You can use document.startViewTransition() in vanilla JavaScript. However, for Multi-Page Apps, using a lightweight router like Astro’s <ClientRouter /> smooths over the current browser inconsistencies (specifically regarding cross-document support in Gecko).

Does this hurt SEO?

No. Unlike SPAs, which often struggle with indexing, Astro ships standard HTML files for every route. The search engine sees a document. The user sees an app.

Is ClientRouter the same as the old ViewTransitions component?

Yes. In Astro 5.0, they renamed <ViewTransitions /> to <ClientRouter />. It handles the routing logic required to keep the state persistent while the View Transitions API handles the visual morphing.