Article cover image

A Guide to Astro Navigation

  • Julien
    Julien
    Full-Stack Developer

Astro is a much-loved framework in the modern web ecosystem. It’s commonly used for pre-generating pages on content-heavy websites. Yet it’s flexible, packed with features, and arguably has everything you need to consider it over alternative (meta)frameworks like Next.js.

This is by no means the “ultimate” guide. There are other fun navigation-related tools you can use with Astro – such as cookies and middleware – that bring even more value. I’ll be sharing a few tips on how we made idx.dev navigation feel magical. Go test for yourself! It should be snappy and consistent across all browsers.

SSG all the way

In your astro config you’re able to change the rendering mode. The default is output: 'static' which doesn’t allow any dynamic functionality. We like to set it to output: 'hybrid' which still pre-generates all pages by default, but allows you to opt-out on a per-page basis using export const prerender = false. We tend to need dynamic on-request handling for api routes – also, when using live preview data sourced from our CMS.

It goes without saying, pre-generated pages makes navigation much faster as the server just has to respond with the pre-packaged resources (html, css, js if any).

Prefetch all links

We opted to use the most aggressive prefetching strategy in our Astro Config. It prefetches all links tags on every page. This is set by default if you’re using ViewTransitions.

import { defineConfig } from 'astro/config';

export default defineConfig({
  prefetch: {
    prefetchAll: true
  }
});

Clean up paths

When using dynamic routes (in our case, nested dynamic routes + pagination), feel free to redirect your “default” routes to shorter ones. Check out our redirects config:

redirects: {
	'/blog/all': '/blog',
	'/blog/all/1': '/blog',
	'/blog/posts/1': '/blog/posts',
	'/blog/articles/1': '/blog/articles'
},

It’s a small touch but we love clean URLs

ViewTransitions

View Transitions is an exciting new browser API giving you granular control over animating pages and elements – in our case, between page navigations. Astro makes adding view transitions a simple one-line change. We added our <ViewTransitions fallback="none"> component to the shared <Layout> we use for every route.

This gives every navigation a smooth transition by default, but there are many ways to customize the look and feel of the animated navigations, so go wild!

Astro makes it convenient to work with the browser API, think of Astro ViewTransitions API as a simplified wrapper around the browser ones. Some of our favorite features:

  • You can designate which html elements are considered the “same” across pages by simply assigning them the same ID, and Astro will automatically animate these elements during navigation for you if it appears on both pages 🤯 Astro docs mentions it will find the element automatically but we’ve had better luck by defining IDs.
  • By default, pages will fade in & out when navigating, but you can set Animation Directives, or even make your own animations! You also have fine-grained control over how each html element will animate. Take this example from the docs, it “…produces a slide animation for the body content while disabling the browser’s default fade animation for the rest of the page”:
---
import CommonHead from '../components/CommonHead.astro';
---

<html transition:animate="none">
  <head>
    <CommonHead />
  </head>
  <body>
    <header>
      ...
    </header>
    <!-- Override your page default on a single element -->
    <main transition:animate="slide">
      ...
    </main>
  </body>
</html>

Note, while using Astro’s ViewTransitions, we noticed some funny behaviors and console errors – for example when using complex <script> tags (e.g., using third-party libraries). This is why we’re using fallback="none" in combination with this strategy for all of our script tags:

<script>
	import { supportsViewTransitions } from 'astro:transitions/client'

	if (supportsViewTransitions) {
		document.addEventListener('astro:page-load', callback)
	} else {
		callback()
	}

	function callback() {
		// script here
	}
</script>

Read mode about script behavior with view transitions here. Feel free to tweak the code above to fit your needs, this strategy seemed to work best for us. Ideally, Astro will work out these issues, and we can use the default ViewTransition fallback. The issues may even be fixed at the time of reading this; it’s just something to keep in mind.

View transitions are supported in Safari 18 beta, and Firefox has announced their upcoming support for it.

Just an innocent “back” button

This isn’t an Astro-specific phenomenon per se – but don’t you hate when you click a site’s custom “back” button and it triggers your browser’s “back” navigation, especially when you came to that page from another site? This is a tricky edge-case to solve (or work around) if you want a “back” button. This nuanced user experience issue can be addressed in many ways, but we opted for the simplest method allowing us to stick to the original design: using local storage. When a user is on any blog feed on our site, we toggle a cameFromFeed key like so:

const feedLinks = document.querySelectorAll('[data-is-feed-link]')
feedLinks.forEach((feedLink) => {
	feedLink?.addEventListener('click', () => {
		localStorage.setItem('cameFromFeed', 'true')
	})
})

We then use it and reset it in our “Back to Blog” Astro component. We also check if the user’s browser history length is greater than 1 on our site.

<a
	href="/blog"
	data-back-to-blog
>
	 Back to Blog
</a>

<script>
	import { supportsViewTransitions } from 'astro:transitions/client'

	if (supportsViewTransitions) {
		document.addEventListener('astro:page-load', callback)
	} else {
		callback()
	}

	function callback() {
		const backButton = document.querySelector('[data-back-to-blog]')
		const fromFeed = localStorage.getItem('cameFromFeed')
		localStorage.removeItem('cameFromFeed')

		backButton?.addEventListener('click', (e) => {
			if (fromFeed && history.length > 1) {
				e.preventDefault()
				history.back()
			}
		})
	}
</script>

In short – if we determine a guest came from our site, we trigger history.back() instead of letting them navigate using the link href. We do this so that the ViewTransitions work properly, and so that they keep their scroll position on the previous page.

Thanks for reading, hope it helps! Find much more info in the Astro docs, which I need to credit as a large resource when making this.Astro is a much-loved framework in the modern web ecosystem. It’s commonly used for pre-generating pages on content-heavy websites. Yet it’s flexible, packed with features, and arguably has everything you need to consider it over a alternative (meta)frameworks like Next.js.