
ねつき
お兄ちゃん〜、昨日のAstro入門の続きだよ♪
今日は動的ルーティングについて説明するね(´∀`)

ねつき
うん!例えばブログ記事が100個あるとするでしょ?
普通に考えたら100個のHTMLファイルが必要だよね。

ねつき
でもAstroなら、1つのテンプレートファイルで100ページ全部生成できるの!(≧∇≦)
コンテンツは別で用意するけど、テンプレートのコピペ地獄からは解放されるよ♪
静的ルーティングのおさらい

ねつき
まず普通のルーティングをおさらいするね。
Astroではsrc/pages/の中のファイル構造がそのままURLになるの♪
src/pages/
├── index.astro → /
├── about.astro → /about
└── blog/
└── index.astro → /blog

ねつき
これが静的ルーティング。ファイル1つ = ページ1つ、シンプルでしょ?(´∀`)
動的ルーティングの基本

ねつき
じゃあ本題!
ブログ記事のURLって、こんな感じになることが多いよね。
/blog/first-post
/blog/second-post
/blog/third-post
...
src/pages/blog/
├── first-post.astro
├── second-post.astro
├── third-post.astro
...(100個続く)

ねつき
100記事あったら100ファイル!(´;ω;`)
しかも中身はほとんど同じテンプレートなのに…

ねつき
そこで動的ルーティングの出番!
ファイル名に[角括弧]を使うの♪
src/pages/blog/
└── [slug].astro → /blog/first-post, /blog/second-post, etc.

ねつき
[slug].astroっていう1つのテンプレートで、すべての記事ページを生成できるんだよ(´∀`)
getStaticPathsで経路を定義

ねつき
動的ルーティングを使うには、getStaticPathsっていう関数をエクスポートするの。
「このページは何パターン生成するか」を教えてあげる関数だよ♪
---
// 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>記事: {slug}</h1>

お兄ちゃん
なるほど、配列で「生成したいパターン」を返すのか。
dist/
└── blog/
├── first-post/index.html
├── second-post/index.html
└── third-post/index.html

ねつき
1つのAstroファイルから、3つのHTMLが生成されたの(≧∇≦)
propsでデータを渡す

ねつき
でも実際のブログって、タイトルとか本文とかあるでしょ?
paramsだけじゃなくてpropsも渡せるの♪
---
// src/pages/blog/[slug].astro
export function getStaticPaths() {
const posts = [
{ slug: 'first-post', title: '最初の記事', content: 'こんにちは!' },
{ slug: 'second-post', title: '2番目の記事', content: 'またね!' },
];
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>

お兄ちゃん
paramsがURLのパラメータで、propsがページに渡すデータか。

ねつき
そうそう!
実際にはMarkdownファイルから読み込んだり、APIから取得したりすることが多いよ♪
複数階層もOK!レストパラメータ

ねつき
もっと深い階層のURLも作れるの!
[...slug]っていう書き方を使うよ♪
---
// 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>ドキュメント: {slug || 'トップ'}</h1>

ねつき
うん!GitHubみたいな複雑なURLも表現できるの(´∀`)
/[org]/[repo]/tree/[branch]/[...file]
↓
/withastro/astro/tree/main/docs/public/favicon.svg

ねつき
こんな複雑なパスも1つのテンプレートで!(゚∀゚)
実際の使用例:このサイトの多言語対応

ねつき
このサイトでも動的ルーティング使ってるんだよ♪
多言語対応(i18n)のために[...lang]を使ってるの。
src/pages/
└── [...lang]/
├── index.astro → /, /en
├── profile.astro → /profile, /en/profile
├── diary/
│ └── [...slug].astro → /diary/2025-12-09, /en/diary/2025-12-09
...

ねつき
日本語版と英語版、同じテンプレートから生成してるの(≧∇≦)
---
// 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>

お兄ちゃん
なるほど、undefinedで日本語版、'en'で英語版になるのか。

ねつき
そうなの♪
デフォルト言語(日本語)はURLに言語コードが付かないようにしてるんだ(´∀`)
ねつき的まとめ

お兄ちゃん
1つのテンプレートで複数ページ生成できるのは便利だね。

ねつき
でしょ?(≧∇≦)
ブログ記事100個 → テンプレートは[slug].astroの1つだけ
商品ページ1000個 → テンプレートは[id].astroの1つだけ
多言語対応 → [...lang]フォルダでテンプレート共通化
コンテンツは別で用意するけど、テンプレートのコピペからは解放されるの♪

ねつき
次回はContent Collectionsについて話そうかな?
Markdownファイルを型安全に扱う方法だよ♪
お楽しみに〜(´∀`)