T-CREATOR

Astro ルーティング早見表:静的/動的/キャッチオール/パラメータ対応

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>

ポイント

  • pathsplit("​/​") で配列に変換すると扱いやすい
  • パンくずリストやサイドバーの生成に活用できる

キャッチオールルーティングは、ドキュメントサイトや階層的なカテゴリページで威力を発揮します。

オプショナルキャッチオールルーティング:ルートも含めて処理

オプショナルキャッチオール は、[[...パラメータ名]] の形式で、ルートパス(​/​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>

ポイント

  • pageundefined かどうかで条件分岐
  • 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​/​introapi​/​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> &gt; </span>
        <a href={crumb.url}>{crumb.label}</a>
      </>
    ))}
  </nav>

  <article>
    <h1>{doc.title}</h1>
    <div>{doc.content}</div>
  </article>
</Layout>

ポイント

  • pathsplit("​/​") で配列化
  • 各階層ごとの 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 のルーティングをマスターしてくださいね。

関連リンク