Astro ルーティング早見表:静的/動的/キャッチオール/パラメータ対応
Astro でサイトを構築する際、ルーティングの仕組みを理解することは欠かせません。ファイルベースのシンプルな設計でありながら、静的・動的・キャッチオールといった多彩なパターンに対応できるのが Astro の魅力です。
本記事では、Astro のルーティングを 4 つの主要パターン に分けて整理し、実務ですぐに使える早見表とコード例をご紹介します。初めて Astro を触る方も、この記事を読めばルーティングの全体像がスッキリ把握できるでしょう。
ルーティング早見表
以下の表で、Astro の主要なルーティングパターンを一覧できます。
| # | ルーティング種類 | ファイルパス例 | URL 例 | 用途 | パラメータ取得 |
|---|---|---|---|---|---|
| 1 | 静的ルーティング | src/pages/about.astro | /about | 固定ページ(会社概要、お問い合わせなど) | 不要 |
| 2 | 動的ルーティング | src/pages/blog/[slug].astro | /blog/hello-astro | ブログ記事、商品詳細など個別ページ | Astro.params.slug |
| 3 | キャッチオールルーティング | src/pages/docs/[...path].astro | /docs/guide/intro | 階層が深いドキュメント、多段カテゴリ | Astro.params.path (配列) |
| 4 | オプショナルキャッチオール | src/pages/blog/[[...page]].astro | /blog or /blog/category/tech | ルートとサブパスを同一ファイルで処理 | Astro.params.page (配列 or undefined) |
この表を手元に置いておけば、どのルーティングパターンを選ぶべきか迷わずに済みますね。
背景
Astro のファイルベースルーティング
Astro は ファイルベースルーティング を採用しており、src/pages/ ディレクトリに配置したファイルが自動的に URL として公開されます。このアプローチは Next.js や SvelteKit といった他のフレームワークでも広く使われており、設定ファイルを書かずに直感的にページを追加できるのが大きな利点です。
例えば、src/pages/about.astro を作成すれば、/about という URL でアクセス可能になります。複雑なルーティング設定を記述する必要がなく、ファイル構造がそのまま URL 構造に反映されるため、プロジェクトの見通しが良くなるでしょう。
静的サイトと動的コンテンツのバランス
Astro は 静的サイト生成(SSG) を基本としつつ、必要に応じて サーバーサイドレンダリング(SSR) にも対応しています。ルーティングの設計次第で、ビルド時に全ページを生成したり、リクエスト時に動的に生成したりと柔軟に使い分けられます。
この柔軟性を活かすには、各ルーティングパターンの特性を理解し、適切に使い分けることが重要です。
以下の図は、Astro のルーティングがどのようにファイル構造と URL に紐づくかを示しています。
mermaidflowchart TD
pages["src/pages/ ディレクトリ"] -->|静的| static["about.astro → /about"]
pages -->|動的| dynamic["blog/[slug].astro → /blog/:slug"]
pages -->|キャッチオール| catchall["docs/[...path].astro → /docs/*"]
pages -->|オプショナル| optional["blog/[[...page]].astro → /blog/*"]
static --> build1["ビルド時に HTML 生成"]
dynamic --> build2["getStaticPaths() で<br/>パスを事前定義"]
catchall --> build3["複数階層を<br/>1 ファイルで処理"]
optional --> build4["ルートとサブパスを<br/>統合管理"]
図で理解できる要点
- ファイル名の記法(
[slug]、[...path])が URL パターンを決定する - 静的ルーティングは設定不要、動的ルーティングは
getStaticPaths()で制御 - キャッチオール系は階層構造を柔軟に扱える
課題
ルーティングパターンの選択に迷う
Astro を初めて使う際、「どのファイル名にすればいいのか」「[slug] と [...path] の違いは何か」といった疑問が生じがちです。特に以下のようなケースで混乱しやすいでしょう。
- ブログ記事のような個別ページを作りたいが、URL 構造をどう設計すればいいか分からない
- ドキュメントサイトで
/docs/guide/intro、/docs/api/referenceのような多階層 URL を実現したい - カテゴリページとルートページを 1 つのファイルで管理したいが、やり方が不明
これらの課題は、ルーティングパターンの特性を理解していないために起こります。
パラメータの取得方法が分かりにくい
動的ルーティングやキャッチオールルーティングでは、URL からパラメータを取得する必要があります。しかし、以下のような点で戸惑うことがあるでしょう。
Astro.paramsでどのようにパラメータにアクセスするか- キャッチオールで取得した値が配列なのか文字列なのか
getStaticPaths()でどうパスを定義すればいいか
これらの疑問を解消しないと、実装段階で手が止まってしまいます。
SSG と SSR の使い分けが曖昧
Astro はデフォルトで SSG ですが、SSR モードに切り替えることも可能です。ルーティング設計においては、以下のような判断が求められるでしょう。
- ビルド時に全ページを生成するか(SSG)
- リクエスト時に動的生成するか(SSR)
- 両者を組み合わせるハイブリッド構成にするか
この使い分けが曖昧だと、パフォーマンスや運用コストに影響が出てしまいます。
解決策
静的ルーティング:固定ページの基本形
静的ルーティング は最もシンプルな形式で、ファイル名がそのまま URL になります。
ファイル配置例
bashsrc/pages/
├── index.astro → /
├── about.astro → /about
└── contact.astro → /contact
このように、about.astro を作成すれば /about でアクセス可能です。会社概要やお問い合わせページのような固定コンテンツに最適ですね。
コード例
src/pages/about.astro の基本的な構成は以下のとおりです。
typescript---
// Frontmatter(TypeScript)
const title = "会社概要";
---
<html lang="ja">
<head>
<title>{title}</title>
</head>
<body>
<h1>{title}</h1>
<p>私たちの会社について紹介します。</p>
</body>
</html>
ポイント
- Frontmatter(
---で囲まれた部分)で変数やロジックを定義 - HTML テンプレート内で
{title}のように変数を展開
静的ルーティングは設定不要で、ファイルを作るだけで即座にページが公開されます。
動的ルーティング:個別ページを柔軟に生成
動的ルーティング は、[パラメータ名] の形式でファイル名を定義することで、URL の一部を変数として扱えます。
ファイル配置例
bashsrc/pages/
└── blog/
└── [slug].astro → /blog/hello-astro、/blog/getting-started など
[slug].astro というファイル名により、/blog/任意の文字列 というパターンの URL を処理できます。
getStaticPaths() でパスを定義
SSG モードでは、ビルド時に生成するパスを事前に定義する必要があります。getStaticPaths() 関数を使ってパスのリストを返します。
typescript---
// src/pages/blog/[slug].astro
export async function getStaticPaths() {
return [
{ params: { slug: "hello-astro" } },
{ params: { slug: "getting-started" } },
{ params: { slug: "advanced-tips" } },
];
}
---
ポイント
paramsオブジェクトでパラメータを指定- この例では
/blog/hello-astro、/blog/getting-started、/blog/advanced-tipsの 3 ページが生成される
パラメータの取得
Astro.params を使って URL パラメータにアクセスします。
typescript---
const { slug } = Astro.params;
// slug に応じてデータを取得(例:Markdown ファイルから)
const post = await getPostBySlug(slug);
---
<html lang="ja">
<head>
<title>{post.title}</title>
</head>
<body>
<h1>{post.title}</h1>
<div>{post.content}</div>
</body>
</html>
ポイント
Astro.params.slugで URL のslug部分を取得- 取得した値を使ってデータフェッチや条件分岐が可能
props でデータを渡す
getStaticPaths() の props を使えば、各ページにデータを事前に渡せます。
typescript---
export async function getStaticPaths() {
const posts = await fetchAllPosts();
return posts.map((post) => ({
params: { slug: post.slug },
props: { post }, // ここでデータを渡す
}));
}
const { post } = Astro.props; // props から受け取る
---
<html lang="ja">
<head>
<title>{post.title}</title>
</head>
<body>
<h1>{post.title}</h1>
<p>{post.publishedDate}</p>
<div>{post.content}</div>
</body>
</html>
ポイント
propsでデータを渡すことで、各ページでのデータフェッチを省略できる- ビルド時に一度だけデータを取得すれば良いので効率的
動的ルーティングは、ブログ記事や商品詳細ページのように 同じテンプレートで複数ページを生成 したい場合に最適です。
キャッチオールルーティング:多階層 URL を一括処理
キャッチオールルーティング は、[...パラメータ名] の形式で、複数階層の URL を 1 つのファイルで処理できます。
ファイル配置例
swiftsrc/pages/
└── docs/
└── [...path].astro → /docs/guide/intro、/docs/api/reference など
[...path].astro により、/docs/ 以下のあらゆる階層を扱えます。
パスの定義
typescript---
// src/pages/docs/[...path].astro
export async function getStaticPaths() {
return [
{ params: { path: "guide/intro" } },
{ params: { path: "guide/advanced" } },
{ params: { path: "api/reference" } },
];
}
---
ポイント
pathは文字列で指定(スラッシュ区切り)/docs/guide/introのような多階層 URL を生成
パラメータの取得
キャッチオールで取得したパラメータは 配列ではなく文字列 として返されます(スラッシュ区切り)。
typescript---
const { path } = Astro.params;
// path = "guide/intro" のような文字列
const segments = path.split("/");
// segments = ["guide", "intro"] のような配列に変換
---
<html lang="ja">
<head>
<title>ドキュメント: {segments.join(" > ")}</title>
</head>
<body>
<nav>
{segments.map((segment, index) => (
<span>{segment}{index < segments.length - 1 ? " > " : ""}</span>
))}
</nav>
<h1>ページ内容</h1>
</body>
</html>
ポイント
pathをsplit("/")で配列に変換すると扱いやすい- パンくずリストやサイドバーの生成に活用できる
キャッチオールルーティングは、ドキュメントサイトや階層的なカテゴリページで威力を発揮します。
オプショナルキャッチオールルーティング:ルートも含めて処理
オプショナルキャッチオール は、[[...パラメータ名]] の形式で、ルートパス(/blog など)とサブパス(/blog/category/tech など)を同一ファイルで処理できます。
ファイル配置例
luasrc/pages/
└── blog/
└── [[...page]].astro → /blog、/blog/category/tech など
通常のキャッチオール([...page])では /blog 自体は扱えませんが、オプショナル([[...page]])なら対応可能です。
パスの定義
typescript---
// src/pages/blog/[[...page]].astro
export async function getStaticPaths() {
return [
{ params: { page: undefined } }, // /blog
{ params: { page: "category/tech" } }, // /blog/category/tech
{ params: { page: "archive/2024" } }, // /blog/archive/2024
];
}
---
ポイント
page: undefinedでルートパス(/blog)を処理page: "category/tech"でサブパスを処理
パラメータの取得と条件分岐
typescript---
const { page } = Astro.params;
let content;
if (!page) {
// /blog の場合
content = "ブログトップページ";
} else {
// /blog/category/tech などの場合
content = `ページ: ${page}`;
}
---
<html lang="ja">
<head>
<title>{content}</title>
</head>
<body>
<h1>{content}</h1>
<p>page パラメータ: {page || "なし"}</p>
</body>
</html>
ポイント
pageがundefinedかどうかで条件分岐- 1 つのファイルでルートとサブパスを統合管理できる
オプショナルキャッチオールは、トップページと一覧ページを 1 つのコンポーネントで扱いたい場合に便利です。
ルーティングパターンの選択フローチャート
どのルーティングを選ぶべきか迷った際は、以下のフローチャートを参考にしてください。
mermaidflowchart TD
start["ルーティングを<br/>選択する"] --> q1{"URL は固定か?"}
q1 -->|はい| static["静的ルーティング<br/>(about.astro)"]
q1 -->|いいえ| q2{"階層は 1 つか?"}
q2 -->|はい| dynamic["動的ルーティング<br/>([slug].astro)"]
q2 -->|いいえ| q3{"ルートパスも<br/>処理するか?"}
q3 -->|はい| optional["オプショナル<br/>キャッチオール<br/>([[...page]].astro)"]
q3 -->|いいえ| catchall["キャッチオール<br/>([...path].astro)"]
図で理解できる要点
- URL が固定なら静的ルーティング
- パラメータが 1 つなら動的ルーティング
- 多階層でルートも処理するならオプショナルキャッチオール
- 多階層のみならキャッチオール
このフローに従えば、適切なルーティングパターンを素早く選択できますね。
具体例
ブログサイトの実装例
ブログサイトでよくある構成を、Astro のルーティングで実装してみましょう。
ディレクトリ構成
bashsrc/pages/
├── index.astro → / (トップページ)
├── blog/
│ ├── [[...page]].astro → /blog、/blog/page/2 など
│ └── [slug].astro → /blog/my-first-post など
└── tags/
└── [tag].astro → /tags/astro、/tags/typescript など
トップページ(静的ルーティング)
typescript---
// src/pages/index.astro
import Layout from "../layouts/Layout.astro";
const recentPosts = await fetchRecentPosts(5);
---
<Layout title="ブログトップ">
<h1>最新記事</h1>
<ul>
{recentPosts.map((post) => (
<li>
<a href={`/blog/${post.slug}`}>{post.title}</a>
</li>
))}
</ul>
</Layout>
ポイント
index.astroで/を処理- 最新記事のリストを表示
ブログ一覧とページネーション(オプショナルキャッチオール)
typescript---
// src/pages/blog/[[...page]].astro
import Layout from "../../layouts/Layout.astro";
const POSTS_PER_PAGE = 10;
export async function getStaticPaths() {
const allPosts = await fetchAllPosts();
const totalPages = Math.ceil(allPosts.length / POSTS_PER_PAGE);
const paths = [
{ params: { page: undefined } }, // /blog
];
for (let i = 2; i <= totalPages; i++) {
paths.push({ params: { page: `page/${i}` } }); // /blog/page/2 など
}
return paths;
}
---
この部分で、ブログトップ(/blog)とページネーション(/blog/page/2 など)のパスを定義しています。
typescript---
const { page } = Astro.params;
const allPosts = await fetchAllPosts();
let currentPage = 1;
if (page && page.startsWith("page/")) {
currentPage = parseInt(page.split("/")[1], 10);
}
const start = (currentPage - 1) * POSTS_PER_PAGE;
const end = start + POSTS_PER_PAGE;
const posts = allPosts.slice(start, end);
---
<Layout title={`ブログ一覧 - ページ ${currentPage}`}>
<h1>記事一覧(ページ {currentPage})</h1>
<ul>
{posts.map((post) => (
<li>
<a href={`/blog/${post.slug}`}>{post.title}</a>
</li>
))}
</ul>
{currentPage > 1 && (
<a href={currentPage === 2 ? "/blog" : `/blog/page/${currentPage - 1}`}>
前のページ
</a>
)}
{end < allPosts.length && (
<a href={`/blog/page/${currentPage + 1}`}>次のページ</a>
)}
</Layout>
ポイント
pageパラメータから現在のページ番号を取得slice()で該当範囲の記事を抽出- 前後ページへのリンクを動的生成
個別記事ページ(動的ルーティング)
typescript---
// src/pages/blog/[slug].astro
import Layout from "../../layouts/Layout.astro";
export async function getStaticPaths() {
const posts = await fetchAllPosts();
return posts.map((post) => ({
params: { slug: post.slug },
props: { post },
}));
}
const { post } = Astro.props;
---
<Layout title={post.title}>
<article>
<h1>{post.title}</h1>
<p>公開日: {post.publishedDate}</p>
<div>{post.content}</div>
<ul>
{post.tags.map((tag) => (
<li><a href={`/tags/${tag}`}>{tag}</a></li>
))}
</ul>
</article>
</Layout>
ポイント
- 各記事ページを
[slug].astroで生成 propsで記事データを渡し、テンプレート内で展開- タグページへのリンクも動的生成
タグ別記事一覧(動的ルーティング)
typescript---
// src/pages/tags/[tag].astro
import Layout from "../../layouts/Layout.astro";
export async function getStaticPaths() {
const allPosts = await fetchAllPosts();
const tags = [...new Set(allPosts.flatMap((post) => post.tags))];
return tags.map((tag) => {
const postsWithTag = allPosts.filter((post) =>
post.tags.includes(tag)
);
return {
params: { tag },
props: { tag, posts: postsWithTag },
};
});
}
const { tag, posts } = Astro.props;
---
<Layout title={`タグ: ${tag}`}>
<h1>タグ「{tag}」の記事一覧</h1>
<ul>
{posts.map((post) => (
<li>
<a href={`/blog/${post.slug}`}>{post.title}</a>
</li>
))}
</ul>
</Layout>
ポイント
- 全記事からタグを抽出し、タグごとのページを生成
propsでタグと該当記事のリストを渡す
このように、Astro のルーティングを組み合わせることで、本格的なブログサイトを構築できます。
ドキュメントサイトの実装例
階層的なドキュメントサイトをキャッチオールルーティングで構築する例です。
ディレクトリ構成
swiftsrc/pages/
└── docs/
└── [...path].astro → /docs/guide/intro、/docs/api/methods など
パスの定義
typescript---
// src/pages/docs/[...path].astro
import Layout from "../../layouts/Layout.astro";
export async function getStaticPaths() {
const docFiles = await fetchAllDocFiles();
return docFiles.map((doc) => ({
params: { path: doc.path }, // "guide/intro" のような形式
props: { doc },
}));
}
const { doc } = Astro.props;
const { path } = Astro.params;
const segments = path.split("/");
---
ここで、ドキュメントファイルのパス(guide/intro、api/methods など)を定義しています。
パンくずリストの生成
typescript---
// パンくずリスト用の URL を生成
const breadcrumbs = segments.map((segment, index) => {
const url = `/docs/${segments.slice(0, index + 1).join("/")}`;
return { label: segment, url };
});
---
<Layout title={doc.title}>
<nav>
<a href="/">ホーム</a>
{breadcrumbs.map((crumb, index) => (
<>
<span> > </span>
<a href={crumb.url}>{crumb.label}</a>
</>
))}
</nav>
<article>
<h1>{doc.title}</h1>
<div>{doc.content}</div>
</article>
</Layout>
ポイント
pathをsplit("/")で配列化- 各階層ごとの URL を生成してパンくずリストを作成
キャッチオールルーティングを使えば、階層が深いドキュメントサイトも 1 つのファイルで効率的に管理できますね。
SSR モードでのルーティング
Astro を SSR モードで使う場合、getStaticPaths() は不要です。リクエスト時に動的にパラメータを取得できます。
SSR モードの設定
javascript// astro.config.mjs
import { defineConfig } from 'astro/config';
import node from '@astrojs/node';
export default defineConfig({
output: 'server', // SSR モードを有効化
adapter: node({ mode: 'standalone' }),
});
ポイント
output: "server"で SSR モードに切り替え- アダプター(Node.js、Vercel、Netlify など)を指定
動的ルーティングの実装
typescript---
// src/pages/products/[id].astro
const { id } = Astro.params;
// リクエスト時にデータベースから取得
const product = await fetchProductById(id);
if (!product) {
return new Response(null, {
status: 404,
statusText: "Not found",
});
}
---
<html lang="ja">
<head>
<title>{product.name}</title>
</head>
<body>
<h1>{product.name}</h1>
<p>価格: {product.price} 円</p>
<p>{product.description}</p>
</body>
</html>
ポイント
getStaticPaths()なしでAstro.paramsからパラメータを取得- 存在しない ID の場合は 404 レスポンスを返す
SSR モードでは、ビルド時にパスを定義する必要がなく、リクエストごとに柔軟に対応できます。在庫状況やユーザーごとのコンテンツなど、リアルタイム性が求められるサイトに最適ですね。
ルーティング優先順位の理解
Astro では、複数のルーティングパターンが競合した場合、以下の優先順位で解決されます。
| # | 優先順位 | パターン | 例 |
|---|---|---|---|
| 1 | 最高 | 静的ルーティング | about.astro |
| 2 | 高 | 動的ルーティング | [slug].astro |
| 3 | 中 | キャッチオールルーティング | [...path].astro |
| 4 | 低 | オプショナルキャッチオール | [[...page]].astro |
優先順位の具体例
以下のようなディレクトリ構成の場合を考えましょう。
bashsrc/pages/
└── blog/
├── about.astro → /blog/about
├── [slug].astro → /blog/:slug
└── [...path].astro → /blog/*
/blog/aboutにアクセス →about.astroが優先される(静的ルーティング)/blog/hello-worldにアクセス →[slug].astroが処理(動的ルーティング)/blog/category/techにアクセス →[...path].astroが処理(キャッチオール)
このように、より具体的なパターンが優先されます。ルーティング設計時にこの優先順位を意識すると、意図しない挙動を防げるでしょう。
まとめ
Astro のルーティングは、ファイルベースのシンプルな設計でありながら、静的・動的・キャッチオール・オプショナルキャッチオールという 4 つのパターンで多様なサイト構造に対応できます。
本記事でご紹介した早見表とコード例を活用すれば、以下のようなサイトを効率的に構築できるでしょう。
- ブログサイト: 個別記事、タグページ、ページネーション
- ドキュメントサイト: 階層的なページ構造、パンくずリスト
- EC サイト: 商品詳細、カテゴリ別一覧
ルーティングの特性を理解し、適材適所で使い分けることが、快適な開発とメンテナンス性の高いプロジェクトにつながります。ぜひこの記事を手元に置いて、Astro のルーティングをマスターしてくださいね。
関連リンク
articleAstro ルーティング早見表:静的/動的/キャッチオール/パラメータ対応
articleAstro の別環境(dev/stg/prd)を切り替える設定と運用フローの作り方
articleAstro × 部分ハイドレーションの効果測定:TTI/INP に与えるインパクト検証
articleAstro でレイアウト崩れが起きる原因を特定する手順:スロット/スコープ/スタイル隔離
articleAstro のレンダリング戦略を一望:MPA× 部分ハイドレーションの強みを図解解説
articleAstro の 環境変数・秘密情報管理:.env とエッジ環境の安全設計
articlegpt-oss 推論パラメータ早見表:temperature・top_p・repetition_penalty...その他まとめ
articleLangChain を使わない判断基準:素の API/関数呼び出しで十分なケースと見極めポイント
articleJotai エコシステム最前線:公式&コミュニティ拡張の地図と選び方
articleGPT-5 監査可能な生成系:プロンプト/ツール実行/出力のトレーサビリティ設計
articleFlutter の描画性能を検証:リスト 1 万件・画像大量・アニメ多用の実測レポート
articleJest が得意/不得意な領域を整理:単体・契約・統合・E2E の住み分け最新指針
blogiPhone 17シリーズの発表!全モデルiPhone 16から進化したポイントを見やすく整理
blogGoogleストアから訂正案内!Pixel 10ポイント有効期限「1年」表示は誤りだった
blog【2025年8月】Googleストア「ストアポイント」は1年表記はミス?2年ルールとの整合性を検証
blogGoogleストアの注文キャンセルはなぜ起きる?Pixel 10購入前に知るべき注意点
blogPixcel 10シリーズの発表!全モデル Pixcel 9 から進化したポイントを見やすく整理
blogフロントエンドエンジニアの成長戦略:コーチングで最速スキルアップする方法
review今の自分に満足していますか?『持たざる者の逆襲 まだ何者でもない君へ』溝口勇児
reviewついに語られた業界の裏側!『フジテレビの正体』堀江貴文が描くテレビ局の本当の姿
review愛する勇気を持てば人生が変わる!『幸せになる勇気』岸見一郎・古賀史健のアドラー実践編で真の幸福を手に入れる
review週末を変えれば年収も変わる!『世界の一流は「休日」に何をしているのか』越川慎司の一流週末メソッド
review新しい自分に会いに行こう!『自分の変え方』村岡大樹の認知科学コーチングで人生リセット
review科学革命から AI 時代へ!『サピエンス全史 下巻』ユヴァル・ノア・ハラリが予見する人類の未来