
Netsuki
Onii-chan~, this is a follow-up to yesterday’s Astro intro♪
Today I’m gonna explain dynamic routing(´∀`)

Netsuki
Yep yep! Say you’ve got 100 blog posts, right?
Normally you’d need 100 HTML files.

Netsuki
But with Astro, one template file can generate all 100 pages!(≧∇≦)
You still gotta prepare the content separately, but no more template copy-paste hell♪
Quick Recap: Static Routing

Netsuki
First, lemme recap regular routing.
In Astro, the file structure inside src/pages/ becomes your URLs♪
src/pages/
├── index.astro → /
├── about.astro → /about
└── blog/
└── index.astro → /blog

Netsuki
This is static routing. One file = one page, super simple right?(´∀`)
Dynamic Routing Basics

Netsuki
Okay, main topic!
Blog post URLs usually look something like this, right?
/blog/first-post
/blog/second-post
/blog/third-post
...

Netsuki
So like, if you try this with static routing…
src/pages/blog/
├── first-post.astro
├── second-post.astro
├── third-post.astro
...(100 more files)

Netsuki
100 posts means 100 files!(´;ω;`)
And they’re all basically the same template…

Netsuki
That’s where dynamic routing comes in!
You use [brackets] in the filename♪
src/pages/blog/
└── [slug].astro → /blog/first-post, /blog/second-post, etc.

Netsuki
With one template called [slug].astro, you can generate all the post pages(´∀`)
Defining Routes with getStaticPaths

Netsuki
To use dynamic routing, you gotta export a function called getStaticPaths.
It tells Astro “here are all the patterns to generate”♪
---
// src/pages/blog/[slug].astro
export function getStaticPaths() {
return [
{ params: { slug: 'first-post' } },
{ params: { slug: 'second-post' } },
{ params: { slug: 'third-post' } },
];
}
const { slug } = Astro.params;
---
<h1>Post: {slug}</h1>

Onii-chan
So you return an array of patterns you want generated.

Netsuki
Right right! When you build, you get this♪
dist/
└── blog/
├── first-post/index.html
├── second-post/index.html
└── third-post/index.html

Netsuki
Three HTML files from one Astro template!(≧∇≦)
Passing Data with Props

Netsuki
But real blog posts have titles and content, right?
You can pass props too, not just params♪
---
// src/pages/blog/[slug].astro
export function getStaticPaths() {
const posts = [
{ slug: 'first-post', title: 'First Post', content: 'Hello!' },
{ slug: 'second-post', title: 'Second Post', content: 'See ya!' },
];
return posts.map((post) => ({
params: { slug: post.slug },
props: { title: post.title, content: post.content },
}));
}
const { slug } = Astro.params;
const { title, content } = Astro.props;
---
<article>
<h1>{title}</h1>
<p>{content}</p>
</article>

Onii-chan
So params is for URL parameters, props is for page data.

Netsuki
Exactly!
Usually you’d load from Markdown or grab stuff from an API♪
Multiple Levels? Rest Parameters!

Netsuki
You can handle deeper URL paths too!
Use the [...slug] syntax♪
---
// src/pages/docs/[...slug].astro
export function getStaticPaths() {
return [
{ params: { slug: undefined } }, // → /docs
{ params: { slug: 'getting-started' } }, // → /docs/getting-started
{ params: { slug: 'guides/routing' } }, // → /docs/guides/routing
{ params: { slug: 'guides/styling' } }, // → /docs/guides/styling
];
}
const { slug } = Astro.params;
---
<h1>Docs: {slug || 'Top'}</h1>

Onii-chan
The ... lets you span multiple levels.

Netsuki
Yep yep! You can even do complex URLs like GitHub(´∀`)
/[org]/[repo]/tree/[branch]/[...file]
↓
/withastro/astro/tree/main/docs/public/favicon.svg

Netsuki
Even paths THIS complex with one template!(゚∀゚)
Real Example: This Site’s i18n

Netsuki
This site uses dynamic routing too♪
We use [...lang] for multi-language support (i18n).
src/pages/
└── [...lang]/
├── index.astro → /, /en
├── profile.astro → /profile, /en/profile
├── diary/
│ └── [...slug].astro → /diary/2025-12-09, /en/diary/2025-12-09
...

Netsuki
Japanese and English versions, both from the same template(≧∇≦)
---
// src/pages/[...lang]/index.astro
export function getStaticPaths() {
return [
{ params: { lang: undefined }, props: { lang: 'ja' } }, // → /
{ params: { lang: 'en' }, props: { lang: 'en' } }, // → /en
];
}
const { lang } = Astro.props;
---
<h1>{lang === 'ja' ? 'ようこそ!' : 'Welcome!'}</h1>

Onii-chan
So undefined gives Japanese, ‘en’ gives English.

Netsuki
Right right!
The default language (Japanese) doesn’t get a language code in the URL(´∀`)
Netsuki’s Wrap-up

Netsuki
Here’s the dynamic routing cheat sheet♪

Onii-chan
Generating multiple pages from one template is pretty handy.

Netsuki
I know right?!(≧∇≦)
100 blog posts → Just one [slug].astro template
1000 product pages → Just one [id].astro template
Multi-language → Share templates with [...lang] folder
You still gotta prepare the content, but no more template copy-paste♪

Netsuki
Next time maybe I’ll talk about Content Collections?
It’s how you handle Markdown files in a type-safe way♪
Stay tuned~(´∀`)