Filtered & Paginated Dynamic Routes with Astro
Learn how to implement filtering and pagination using dynamic routes in Astro. This guide expands on Astro's documentation with a detailed, practical example.
Skip straight to the result if you’re just here for the smelly EXE↗.
Concepts
What does filtered and paginated dynamic routes mean?
It’s likely what you are looking for if you’re building a content focused static site.
Breaking it down:
- Dynamic Routes: Routes generated at build time based on the content of your site,
e.g./blog/this-article
,/blog/that-article
. - Filtered Routes: Routes generated with a filtered set of the content on your site,
e.g./blog/tutorials
,/blog/articles
- Paginated Routes: Routes with content spread across multiple pages,
e.g./blog/2
,/blog/3
.
While these pages might display different sets of content, it’s likely the design and layout will be mostly the same. This is where this guide comes in.
The Problem
In contrast to dynamic routes, static routes are generated based on the structure of your site.
Imagine we have a dozen or so markdown articles and we want to generate a page for each, along with a page at /blog
to browse avaliable articles. The default Astro blog template provides us with this starting point:
As more content is added, we want to offer users an easier way to navigate articles with result filtering and pagination. So we duplicate the index.astro
page into different folders, updating which posts we are fetching in each new static route:
While this works, it’s clear this is not the right approach.
If we were instead to use dynamic routes, a single file could generate all of our unfiltered, filtered, and paginated routes for us, resulting in a much more maintainable codebase. getStaticPaths()
has an optional argument paginate
that handles pagination for us. Using that, we can replace our static routes with a dynamic page parameter [...page].astro
:
Much better. This is what I’ll be going through how to do in this guide.
Implementation
Jump straight to #pagination or #filtering if you’re already familiar with the basics, or if you have a site set up and ready to go. Otherwise I’ll walk through the initial setup.
Getting Started
For this guide I will be using the official Astro blog template as a starting point.
Like in the example before, our src/pages/
starting point now looks like this:
Navigate to src/content/
and see five markdown files that we’ll be using to generate our dynamic routes. But before we can begin filtering posts we need something to filter by.
Edit config.ts
adding a tag
key to the blog schema↗.
Then let’s make zod happy by adding the new tag
field to our markdown files.
Pagination
In order to create a dynamic route for pagination, we need to rename index.astro
to [page].astro
. If you want the first page of results to drop the page index—i.e. /blog
rather than /blog/1
—make it a rest param like so [...page].astro
.
If you were running the dev server while renaming, you will have seen the following message[ERROR] [GetStaticPathsRequired] ...
in the console.
Let’s go ahead and open [...page.astro]
to implement what it’s asking. As we’re going to be using the paginate()
function to generate our paginated routes, we’ll need to pass it into getStaticPaths()
as it’s an optional argument.
You can configure the number of posts per page using the pageSize
option. As we only have a few posts, I’m setting it to 3 in order to see what we’re doing for now.
Finally, we just need to update the posts map to use page.data
instead. This is where the array of entries we are passing as the first argument to paginate()
end up.
And that’s it. Navigate manually to /blog/2
to see the pagination in action. Of course, you’ll need to add some UI elements to make it possible for users to navigate between pages. Reading the page prop reference↗ gets you there, or otherwise I’ve made a simple UI component↗ to get you started.
Filtering
As with pagination, since we are looking to create a new dynamic route we will create a new dynamic route param. However, this time we’ll do so by moving [...page].astro
into a new folder [...filter].astro
:
Now we need to update our getStaticPaths()
function to return a different set of paginated results for each filter—the tag we added to each post and defined in our schema.
Since we are using a rest ...
parameter in our [...filter]/
page route, leaving the filter param undefined
when returning our unfiltered entries generates those pages at the root /blog/
.
I’ve adjusted our results per page pageSize: 1
to ensure we generate multiple pages in our filtered routes to test. Let’s also quickly enhance our cards so we can see better see if things are working as expected:
Test the filtered routes working by navigating to the url /blog/tutorial
. Again, this isn’t a very user friendly way to navigate, so heres a simple tag cloud component↗ to help get you started.
Result
If you’ve followed along you should now have Filtered & Paginated Dynamic Routes working. Otherwise you can check out my example github repo↗.
I hope this guide helped with understanding some of the concepts and implementation details behind dynamic routes in Astro.
Reach out with any questions or feedback on Twitter↗, or by opening an issue↗ on the repo.