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:

  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 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...