Dynamic routing in Astro with Sanity CMS

I was recently building a project for a client using Astro, which comes with out-of-the-box routing and content management features.

The content management features in Astro are great, but I wanted to use a different tool to make it easier for the client to edit the content: Sanity. Setting up the integration was straightforward with the Sanity integration for Astro.

I have plenty of good things to say about Astro and Sanity. That said, I ran into one main issue: Dynamic routing using Sanity document entries.

Project setup

Astro uses file-based routing, meaning each file in the src/pages/ directory represents a route. The page structure of my project looked something like this:

- index.astro (/)
- secondPage.astro (/secondpage)
- /blog
- index.astro (/blog)
- [slug].astro (/blog/[slug])
- ...additional pages

.astro is the file extension for standard astro files, but Astro supports defining pages with other file types too.

Routing between static pages is simple. Add an <a> element with an href attribute to the relevant page, and you're done. This works for /, /secondpage/, and /blog above.

Routing at /blog/[slug] uses dynamic routing to generate a page at each relevant route that matches /blog/[slug].

Dynamic routes in Astro

Astro offers two options for handling dynamic routes:

1. Static (SSG) - Routes are determined and pages are generated at build time.

2. Server (SSR) - Pages are generated when a matching route is requested from the server.

Because the project was a static website for professional info and blog posts without much interactivity, I decided to use dynamic routing in SSG mode for improved performance and simplified routing.

Typically, this means I would define the routes ahead of time using the built-in function getStaticPaths().

---
export function getStaticPaths() {
return [
{params: {dog: 'clifford'}},
{params: {dog: 'rover'}},
{params: {dog: 'spot'}},
];
}

const { dog } = Astro.params;
---

<div>
Good dog, {dog}!
</div>

A simple example from Astro's SSG routing documentation.

Dynamic routes and content collections

This approach works smoothly when using Astro's built-in content collections for content management.

---
// src/pages/posts/[id].astro

import { getCollection, render } from 'astro:content';
// 1. Generate a new path for every collection entry
export async function getStaticPaths() {
const posts = await getCollection('blog');
return posts.map(post => ({
params: { id: post.id },
props: { post },
}));
}
// 2. For your template, you can get the entry directly from the prop
const { post } = Astro.props;
const { Content } = await render(post);
---
<h1>{post.data.title}</h1>
<Content />

Example from Astro docs.

First, fetch all of the items in the blog collection.

const posts = getCollection('blog');

Then use the id from each post to set the id URL param (note that the route is /posts/[id]), and the post object as the prop for the page.

return posts.map(post => ({
params: { id: post.id },
props: { post },
}));

Now that the routes are generated, the post prop can be used to actually populate the page content.

---
// Prior code above...

const { post } = Astro.props;
const { Content } = await render(post);
---
<h1>{post.data.title}</h1>
<Content />

With Astro collections, we'd be done!

With Sanity, things are a bit more complicated.

Sanity document collections

Coming soon...