Day 25: View Transitions

With one day left before wrap-up, I decided to tackle something I’d been eyeing since the beginning: Astro’s View Transitions API. It felt risky—this feature touches every page and could break things in subtle ways. But the potential payoff was too good to ignore: smooth, app-like navigation that makes the site feel polished and intentional.

The Risk Was Real

Adding View Transitions on Day 25 meant touching code that had been stable for weeks. Scroll animations, analytics tracking, React components—all of it needed to play nicely with Astro’s client-side navigation. One wrong move and I’d spend the final day debugging instead of wrapping up.

But sometimes you have to trust the foundation you’ve built. Twenty-four days of tests, property-based verification, and careful architecture gave me the confidence to try.

What Got Implemented

Core View Transitions Setup

The foundation was straightforward: add Astro’s ViewTransitions component to both layouts with fallback="swap" for browsers that don’t support the API natively.

import { ViewTransitions } from 'astro:transitions';

<ViewTransitions fallback="swap" />

Logo Morphing

One of the most satisfying details: the logo now smoothly morphs between header and footer positions during page transitions. Adding transition:name="site-logo" to both logo elements creates a visual thread that ties navigation together.

Scroll Animation Refactoring

This was the biggest undertaking. The existing scroll-triggered animations used IntersectionObservers that initialized once on page load. With View Transitions, pages don’t fully reload—so those observers needed to be reinitializable.

The refactor introduced:

  • Module-level state with an initScrollReveal function
  • astro:page-load listener to reinitialize animations after navigation
  • astro:before-swap listener to cleanup observers before page swap
  • Proper cleanup to prevent memory leaks

Analytics Cleanup

View Transitions introduced a subtle bug risk: event listeners that persist across navigations could fire multiple times or leak memory. New cleanup functions were added:

  • cleanupExternalLinkTracking() - removes click listeners before page swap
  • cleanupScrollTracking() - cleans up scroll depth tracking

Focus Management

Accessibility during transitions required attention. Astro handles focus automatically when using default ViewTransitions, but I added documentation and property tests to verify:

  • No custom swap overrides interfere with focus handling
  • Global CSS includes focus-visible styles
  • Interactive components maintain proper focus styling

React Component Compatibility

The Assessment form and MobileMenu are React islands that hydrate on the client. View Transitions could break their state or prevent re-hydration. E2E tests verified:

  • Assessment component re-hydrates correctly after navigation from other pages
  • Form state persists during same-page interactions
  • MobileMenu open/close state works properly after transitions
  • Back navigation doesn’t break component state

The Testing Strategy

Given the risk, testing was comprehensive:

Property-based tests verified:

  • ViewTransitions presence in all layouts
  • Consistent transition:name values for logos
  • Reduced motion preferences are respected
  • Focus management follows accessibility requirements

E2E tests covered:

  • React component re-hydration after navigation
  • Form state persistence
  • Mobile menu behavior across transitions
  • Visual regression at adjusted thresholds

What I Learned

Trust your test suite. The comprehensive testing from earlier days gave me confidence to make this change. Without those property tests and E2E coverage, I wouldn’t have attempted this on Day 25.

View Transitions require lifecycle awareness. Code that runs once on page load needs to be refactored to handle re-initialization. This isn’t just about the feature working—it’s about preventing memory leaks and duplicate event handlers.

Small details compound. The logo morphing effect took minutes to implement but adds polish that users feel even if they can’t articulate why.

Cleanup is as important as setup. Half the work was ensuring things get torn down properly before navigation, not just initialized after.

The Result

The site now feels like an app. Navigation is smooth, the logo creates visual continuity, and none of the existing functionality broke. The scroll animations, analytics, and React components all work exactly as they did before—just with nicer transitions between pages.

Was it risky? Yes. Was it worth it? Absolutely.

Tomorrow we wrap up.