Published on February 24, 2025
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:
- Static (SSG) - Routes are determined and pages are generated at build time.
- 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 elected 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.
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...