How to Create Dynamic Routes in Astro
Routes in applications are essential for defining the structure of navigation, enabling users to access specific pages or functionalities seamlessly, which, in turn, enhances user experience and engagement.
Well-designed routes also facilitate efficient code organization and maintainability, making it easier for developers to manage and update the application as it grows in complexity. In this tutorial, we'll take a quick look at how Astro handles routing with its file-based approach.
How Routing Works
Astro uses a file-based routing system to generate URLs at build time. This means that URLs will be generated based on the file's name. To create routes in Astro, all route files must be created inside the src/pages
folder. Let's see a couple of examples of how different files will generate different routes. Take the following table as an example:
Route file | Generated URL |
---|---|
src/pages/index.astro | example.com |
src/pages/about.astro | example.com/about |
src/pages/about/index.astro | example.com/about |
src/pages/about/webtips.astro | example.com/about/webtips |
Each file in the src/pages
folder specifies a route. When using index.astro
directly inside src/pages
, we can generate the home page. When index.astro
is used inside a subfolder, for example /about
, it'll generate a URL with the subfolder's name. Note that in this case, we can either use a file-based or directory-based approach, as both about.astro
and about/index.astro
will match the same URL.
Generating Dynamic Routes
Astro is also capable of generating dynamic routes with custom file names. This way, we can use a single file to generate many pages with different types of data. For example, to generate posts using a single file, we can create a file inside the pages
folder called [post].astro
. This syntax informs Astro that the file is a dynamic route. But how do we define the URLs this way? Let's see an example:
---
export const getStaticPaths = () => {
return [
{ params: { post: 'intro' }},
{ params: { post: 'tutorial' }},
{ params: { post: 'conclusion' }}
]
}
const { post } = Astro.params
---
<h1>{post}</h1>
Here we exported a function specific to Astro called getStaticPaths
. This function must return an array of objects with a params
property. The params.post
property defines the URL of the generated page, meaning we'll have three generated routes:
/post/intro
/post/tutorial
/post/conclusion
We can grab parameters generated through the getStaticPaths
function using Astro.params
. Here we display the post
parameter in an h1
, whose value will either be intro
, tutorial
, or conclusion
.
The property inside the params
object must always be named after the file. For example, if we have a file called [blog].astro
, the property must also be called blog
. Note how the property name changes based on the name of the file:
// Using [post].astro as the file name:
export const getStaticPaths = () => {
return [
{ params: { post: 'intro' }},
{ params: { post: 'tutorial' }},
{ params: { post: 'conclusion' }}
]
}
// Using [blog].astro as the file name:
export const getStaticPaths = () => {
return [
{ params: { blog: 'intro' }},
{ params: { blog: 'tutorial' }},
{ params: { blog: 'conclusion' }}
]
}
Dynamic Nested Routes
This will work for most cases; however, it's not functional with nested URLs. For example, we might need to have the following URL structure on our website:
ββ post/astro/intro
ββ post/astro/tutorial
ββ post/astro/conclusion
To support nested URLs, we need to include rest parameters in the file name to get Astro to match file paths of any depth. This can be achieved by simply renaming the file, in our case from [post].astro
to [...post].astro
, and updating the URLs in the getStaticPaths
function:
---
export const getStaticPaths = () => {
return [
{ params: { post: 'astro/intro' }},
{ params: { post: 'astro/tutorial' }},
{ params: { post: 'astro/conclusion' }}
]
}
const { post } = Astro.params
---
<h1>{post}</h1>
The ...
in the file name indicates that any number of depths can be used in the URL in this route. This way, we can dynamically generate nested routes with a single file. This approach is especially useful when we can have any URL, but want to use the same template for all pages.
Note that the URLs inside the getStaticPaths
function doesn't have a forward or trailing slash.
Using this approach, we can also generate the homepage by simply including an object in the array where the property is undefined
:
---
export const getStaticPaths = () => {
return [
{ params: { route: undefined }},
{ params: { route: 'astro/intro' }},
{ params: { route: 'astro/tutorial' }},
{ params: { route: 'astro/conclusion' }}
]
}
const { route } = Astro.params
---
<h1>{route || 'Home'}</h1>
How to Use Props in Dynamic Routes
When working with dynamic routes, in most cases we'll need to pass data alongside the URL. To achieve this, we can use the props
object. Take a look at the following example:
---
export const getStaticPaths = () => {
const posts = [
{
url: 'intro',
title: 'Introduction to Astro'
},
{
url: 'conclusion',
title: 'Conclusion'
}
]
return posts.map(post => ({
params: { post: post.url },
props: {
...post
}
}))
}
const { title } = Astro.props
---
<h1>{title}</h1>
Here we used a map
to generate the necessary array of objects from the posts
array. We can inject the entire post
object as a prop
using the spread operator. Now we can access the properties of the object through Astro.props
, just like we would access props in a regular Astro component.
Route Priority
Since we can have multiple dynamic routes in the same folder, it's possible for multiple routes to match the same URL. For example, given the following folder structure, all files can match for /post/intro
:
src/pages/post
ββ intro.astro
ββ [post].astro
ββ [...postID].astro
Because of this, Astro uses a priority order to decide which file should be used for the path when building the project. It follows the following priority order:
- Use static routes without path parameters
- Use dynamic routes with named parameters
- Use pre-rendered dynamic routes
- Use routes with rest parameters
Endpoints always take precendence over pages.
Given this priority order, based on the above example, we can conclude that the /post/intro
URL will be matched by intro.astro
. Any other URLs (such as /post/conclusion
) will be matched by [post].astro
, while nested URLs will be matched by [...postID].astro
, e.g., /post/tutorial/intro
.
How to Handle Redirects
Astro is also capable of handling both static and dynamic redirects. We can define redirects in our astro.config.mjs
file using the redirects
property:
import { defineConfig } from 'astro/config'
export default defineConfig({
redirects: {
'/from': '/to',
'/intro': '/introduction',
'/posts/[...post]': '/blog/[...post]'
}
})
Note that we can also bulk redirect dynamic routes as long as the same parameters are used. By default, Astro will generate these pages with a meta refresh tag, which will automatically redirect the pages to the new URL when someone navigates to the old page.
When an adapter is used, it'll write the necessary configuration file instead. For example, if the project is hosted on Netlify, and Netlify is configured in Astro, it'll create a _redirects
file with the correct syntax. When using SSR, we can also specify the status code in the configuration file:
import { defineConfig } from 'astro/config'
export default defineConfig({
redirects: {
'/from': {
destination: '/to',
status: 302
}
}
})
In this case, we need to assign an object to the URL that we want to redirect and use the destination
property for the new URL. Alongside this, we can pass the status
property to set the HTTP response status.
Excluding Pages
Last but not least, we can also exclude pages from being built by prefixing them with an underscore (_
). This is useful for temporarily disabling pages, or to group styles, tests, or utility functions together in the same folder. Take the following folder structure as an example:
src/pages/post
ββ [...post].astro
ββ _postController.js
ββ _post.scss
In this example, the _postController.js
and _post.scss
files will be ignored and only [...post].astro
will be taken into consideration. Of course, we can also disable .astro
files using the same approach.
Conclusion
In summary, Astro's file-based routing comes with many built-in functionalities that enable us to generate routes in many possible different configurations. It's capable of handling both static and dynamic routes, handling redirects, and we can also exclude pages. It comes with a robust priority order to decide which file to use when multiple files can match the same URL.
Do you have any experience with Astro's file-based routing? Leave your thoughts in the comments below on how you feel about using it in the framework. Thank you for reading through; happy coding!
Rocket Launch Your Career
Speed up your learning progress with our mentorship program. Join as a mentee to unlock the full potential of Webtips and get a personalized learning experience by experts to master the following frontend technologies: