Thoughts .toString()

Astro: Dynamic routes with params and pagination


The internet is replete with examples of creating dynamic routes using Astro. Look for paginated routes with parameters and Typescript, however, and the results all seem a little incomplete. For a beginner like I am, it wasn’t entirely clear how the different examples in the documentation work together. So let’s walk through this.

Dissecting getStaticPaths

I think the first thing that was slightly confusing for me was that non-paginated routes look like this for example:

/src/pages/tag/[tag].astroastro
1---
2import type { GetStaticPaths } from 'astro';
3import { getCollection } from 'astro:content';
4export const getStaticPaths = (async () => {
5    const posts = await getCollection('blog');
6    const tags = getTags() // get tags from somewhere
7    return tags.map((tag) => {
8        const filteredPosts = posts.filter((post) =>
9            post.data.tags?.includes(tag));
10        return {
11            params: { tag },
12            props: { post: filteredPost }
13        };
14    });
15}) satisfies GetStaticPaths;
16---

However, according to Astro’s Routing Reference, this is what paginated routes look like:

/src/pages/tag/[tag]/[page].astroastro
1---
2import type { GetStaticPaths } from 'astro';
3import { getCollection } from 'astro:content';
4export const getStaticPaths = (async () => {
     const posts = Object.values(import.meta.glob('../pages/content/blog/*.md', { eager: true })); 
5    const posts = await getCollection('blog'); 
6    const tags = getTags() // get tags from somewhere
7    return tags.flatMap((tag) => {
8        const filteredPosts = posts.filter((post) => 
9            post.frontmatter.tag === tag);
10        return paginate(filteredPost, {
11            params: { tag },
12            // props: { }, optional
13            pageSize: 10
14        });
15    });
 }); 
16}) satisfies GetStaticPaths; 
17---

Slightly altered from Astro’s Routing Reference

First, note the change in structure from the posts being passed as props to being a direct argument of paginate. It would have been more clear if it still passes through props, maybe with a predetermined property name, and whereas maybe pageSize becomes a direct argument in my opinion.

Second, despite this being the latest version of the docs, the example uses outdated constructs like import.meta.glob. It also avoids any Typescript typing (that exists from prior examples from the same page), which begs the question whether e.g. satisfies GetStaticPaths is even compatible with pagination. I’m happy to report that it is, and the example in the documentation is just outdated.

Some pitfalls

Of particular, I had a question of why flatMap is necessary, since it seems like a single object is being returned. But upon closer inspection, the paginate function actually wraps each single result with an array. I found this to be a pitfall, since it was unintuitive why that would be the case.

Continuing on, Typescript Configuration in Astro’s docs suggests to infer getStaticPaths() types.

/src/pages/tag/[tag]/[page].astroastro
1---
2import type { 
3    InferGetStaticParamsType, 
4    InferGetStaticPropsType 
5} from 'astro';
6
7// getStaticPaths function
8
9type Params = InferGetStaticParamsType<typeof getStaticPaths>;
10type Props = InferGetStaticPropsType<typeof getStaticPaths>;
11
12const { tag } = Astro.params as Params;
 // { tag: string } 
13// { tag: never } 
14const { page } = Astro.props;
15---

Slightly altered from Astro’s Typescript Configuration

Strangely, I found that the params inference always returned “never” types instead of “string” as it should. Additionally, VSCode reports correct inference of the props type even without InferGetStaticPropsType.

It seems visually odd that it isn’t const {page} = Astro.props as Props, but adding this causes the component to silently fail to recognize the prop, even though the prop clearly has been passed in, which could be confirmed with a console.log(page); statement.

In the end, I find that the following works just as well, if not better.

/src/pages/tag/[tag]/[page].astroastro
1---
2// getStaticPaths function
3
 type Params = InferGetStaticParamsType<typeof getStaticPaths>;
 type Props = InferGetStaticPropsType<typeof getStaticPaths>;
4type Params = { tag: string };
5
6const { tag } = Astro.params as Params; // { tag: string }
7const { page } = Astro.props;
8---