【Astro】動的ルーティングとは?getStaticPathsの書き方

【Astro】動的ルーティングとは?getStaticPathsの書き方

[id].astro っていうファイルを作ったら、getStaticPaths() が必要ですって怒られたんだけど…

作るURLの一覧を先に用意しろ、って言われてもピンとこないんだよね。

Astroのページは、src/pages にファイルを置くだけでURLになります。
ルーティング用の設定ファイルにパスを1行ずつ書き足していく必要はありません。

ところが「1件ごとに違うURL」を作りたくなった瞬間、話が少しだけ変わります。
[id].astro のようなファイルを用意すると、getStaticPaths() を書けというエラーで止まってしまうからです。

これは不具合ではなく、先にHTMLを書き出しておく作り方から来ている決まりごとです。作るURLを全部教えてもらわないと、Astroは何枚のページを生成すればいいのか決められないんですよね。

この記事では、Astroの動的ルーティングを ファイルの置き場所 → getStaticPaths() → 応用 の順に整理していきます。

この記事でわかること
  • 静的ルートと動的ルートの違いと、ファイル名の [ ] が何をしているか
  • getStaticPaths() が返す params と props の役割の違い
  • [...rest] を使って階層の深いURLをまとめて作る書き方
  • paginate() によるページ分割と、SSRに切り替えたときの扱い

記事データそのものを型付きで管理する方法は、別記事にまとめています。

この記事の前提(2026年9月時点)
  • コードは Astro v7系(最新は7.3.1) の公式ドキュメントに沿った書き方です
  • getStaticPaths の基本的な書き方は v5系でも同じです(このブログ自体はv5.16系で動いています)
  • 公式リファレンスは Routing guide と Routing reference にあります

Astroのルーティングは「ファイルの置き場所」で決まる

Astroのルーティングは、ファイルベースルーティングと呼ばれる方式です。
src/pages の中のファイルパスが、そのまま公開されるURLになります。

ここに置いたファイルには、静的ルートと動的ルートの2種類があります。

静的ルート|src/pages に置いたファイルがそのままURLになる

まずは基本形です。ファイルを1つ置けば、ページが1つできます。

src/pages/
├── index.astro          →  /
├── about.astro          →  /about
└── members/
    ├── index.astro      →  /members
    └── profile.astro    →  /members/profile

index.astro はそのフォルダ自体のURLになる、というのがポイントです。
ページが5枚なら5ファイル。分かりやすい代わりに、ページ数のぶんだけファイルが要ります。

動的ルート|ファイル名を [ ] で囲む

では、メンバーが100人いたらどうするか。100ファイル作るのは現実的ではありません。

そこで使うのが動的ルートです。ファイル名の一部を [ ] で囲むと、その部分が差し替え可能な「穴」になります。

src/pages/members/[id].astro   →  /members/taro, /members/hanako, ...
動的ルート(dynamic route)

1つのファイルから、URLの一部だけが違う複数のページを作るルートのことです。
[id].astro の id の部分に入る値をパラメータと呼び、ページ側から読み取れます。

ただし、このファイルを作っただけではビルドが通りません。
静的(SSG)モードでは、動的ルートのファイルに getStaticPaths() の export が必須だからです。冒頭のエラーはこれです。

理由はシンプルで、[id] に何が入るのかをAstroは知らないから。
taro なのか hanako なのか、あるいは1万件あるのか。その一覧を渡す係が getStaticPaths() というわけです。

getStaticPathsは「作るURLの一覧」を返す関数

getStaticPaths() は、{ params, props } という形のオブジェクトを配列で返す関数です。
返した配列の要素数が、そのままビルドで生成されるページ数になります。

順番に見ていきます。

1. params でURLを組み立てる

いちばん小さい形がこれです。params だけを返します。

---
// src/pages/members/[id].astro
export function getStaticPaths() {
  return [{ params: { id: "taro" } }, { params: { id: "hanako" } }];
}

const { id } = Astro.params;
---

<h1>{id} さんのページ</h1>

これで /members/taro と /members/hanako の2枚のHTMLが書き出されます。

大事なのは、params のキー名です。ファイル名の [ ] の中と一致させる必要があります。
ファイルが [id].astro なら、params のキーも id です。

そして params に入れた値が、URLの一部としてそのまま使われます。ページ側では Astro.params から受け取れます。

2. props でページにデータを渡す

とはいえ、実際に表示したいのはIDだけではありません。名前も役割も出したいはずです。

そこで使うのが props です。まず、元になるデータを用意しておきます。

// src/data/members.ts
export const members = [
  { id: "taro", name: "たろう", role: "デザイン" },
  { id: "hanako", name: "はなこ", role: "開発" },
];

このデータを map() で回して、URLの一覧に変換します。

---
// src/pages/members/[id].astro
import { members } from "../../data/members";

export function getStaticPaths() {
  return members.map((member) => ({
    params: { id: member.id },
    props: { member },
  }));
}

const { member } = Astro.props;
---

<h1>{member.name}</h1>
<p>{member.role}</p>

props は任意です。書かなくてもページは作れます。
ただし、これを使わないとページ側でもう一度データを探し直すことになるので、APIやファイルから取ってきたデータをそのまま渡してしまうのが定石です。

らるじゅらるじゅ

props で渡しておくと、ページ側は受け取って表示するだけになります。
「一覧を作る側」と「1枚を描く側」の役割がきれいに分かれるのが気持ちいいところ。

3. Astro.params と Astro.props の使い分け

2つは名前が似ていますが、役割はまったく別物です。

観点Astro.paramsAstro.props
中身ファイル名の [ ] に入れた値自分で渡した任意のデータ
URLへの影響◎ URLそのものになる× まったく影響しない
省略× できない(URLが決まらない)◎ できる(任意)
向いている使い道どのページなのかの判別表示に使うデータの受け渡し

覚えておきたいのは、props はURLに影響しないということ。
props にどれだけデータを積んでも、生成されるURLは params だけで決まります。

TypeScriptを使っているなら、公式サンプルにある satisfies の形で型を付けられます。

import type { GetStaticPaths } from "astro";
import { members } from "../../data/members";

export const getStaticPaths = (() => {
  return members.map((member) => ({
    params: { id: member.id },
    props: { member },
  }));
}) satisfies GetStaticPaths;

こう書いておくと、Astro.params と Astro.props の中身に補完が効きます。

[...rest] で階層の深いURLをまとめて作る

[id].astro が担当できるのは、/members/taro のような1階層ぶんだけです。

では /docs/guide/routing のように、階層の深さがページごとに違う場合はどうするか。
ここで使うのが rest parameter です。[...path].astro のように、[ ] の中に ... を付けて書きます。

---
// src/pages/docs/[...path].astro
export function getStaticPaths() {
  return [
    { params: { path: undefined } },
    { params: { path: "start" } },
    { params: { path: "guide/routing" } },
  ];
}

const { path } = Astro.params;
---

<h1>{path ?? "ドキュメントのトップ"}</h1>

このファイル1枚で、次の3つのURLが生成されます。

/docs                 ← path が undefined
/docs/start           ← path が "start"
/docs/guide/routing   ← path が "guide/routing"

注目したいのは1行目です。rest parameter に undefined を返すと、その階層のトップページにマッチします。
一覧ページと詳細ページを1ファイルで面倒みられるので、index.astro を別に用意しなくて済みます。

rest parameter は、他の名前付きパラメータと組み合わせることもできます。
たとえば src/pages/[lang]/[...path].astro なら、言語ごとにドキュメント全体を作る、といった構成が1ファイルで書けます。

ひとつ気をつけたいのが、値のデコードです。getStaticPaths() が返したパラメータの値は、自動ではデコードされません。
デコード済みの文字列が必要な場面では、decodeURI() を通してから使ってください。

一覧ページの分割は paginate() に任せる

メンバーが100人いるなら、一覧ページも分割したくなります。
これも動的ルートの応用で、paginate() という専用のヘルパーが用意されています。

getStaticPaths() の引数から受け取って、そのまま return するだけです。

---
// src/pages/members/[page].astro
import { members } from "../../data/members";

export function getStaticPaths({ paginate }) {
  return paginate(members, { pageSize: 10 });
}

const { page } = Astro.props;
---

<ul>
  {page.data.map((member) => <li>{member.name}</li>)}
</ul>

paginate() がやってくれるのは2つです。必要なページ数ぶんのパスを自動で作ることと、各ページに page というpropを渡すこと。

詳細ページの [id].astro と同じフォルダに置いても、生成されるURLが重ならなければ動きます。
ただ、同じ階層に動的ルートが2枚ある状態になるので、一覧と詳細でフォルダを分けておくほうが後から迷わないと思います。

pageSize の既定値は 10件です。1ページの件数を変えたいときだけ書けば大丈夫です。
渡せるオプションは pageSize のほかに params props format の3つ。このうち format(URLの整形)は v7.1.0で追加された新しいオプションです。

page プロップに入っているもの

渡ってくる page の中身は、ページ送りのUIを作るのに必要なものが一式そろっています。

プロパティ中身
page.dataそのページに表示する分の配列
page.start / page.endそのページの最初と最後の通し番号(0始まり)
page.total全体の件数
page.currentPage現在のページ番号(1始まり)
page.size1ページあたりの件数
page.lastPage最後のページ番号
page.url.current現在のページのURL
page.url.prev / page.url.next前後のページのURL。無ければ undefined
page.url.first / page.url.last最初と最後のページのURL

currentPage は1始まり、start と end は0始まりです。ここだけ数え方が違うので、表示に使うときは気をつけてください。
なお現在のページ番号は、Astro.params.page から取ることもできます。

page.url.prev が undefined かどうかで、「前へ」のリンクを出すか決めればいいんだね!

TypeScriptで引数に型を付けるなら、import type { GetStaticPathsOptions } from "astro" を使います。

SSRに切り替えると getStaticPaths は要らなくなる

ここまでの話は、すべてビルド時にHTMLを書き出す前提でした。
リクエストが来てからページを組み立てる方式に切り替えると、話がまるごと変わります。

オンデマンドレンダリング(SSR)

アクセスがあったタイミングでサーバー側がページを生成する方式です。
URLの一覧を先に決める必要がないので、getStaticPaths() は使いません。Astro.params をその場で読んでレンダリングします。

2つの違いを並べると、こうなります。

観点静的(SSG)オンデマンド(SSR)
URLが決まるタイミングビルド時リクエストが届いたとき
getStaticPaths()◎ 動的ルートでは必須× 使わない
返すものビルド済みのHTMLその場で生成したHTML
必要な準備なしアダプター

切り替えの単位は、ページごとです。prerender をexportして指定します。

---
// src/pages/members/[id].astro
export const prerender = false;

const { id } = Astro.params;
---

<h1>{id} さんのページ</h1>

ここで押さえておきたいのが、prerender の既定値がモードによって違うことです。

  • staticモード … 既定は true。一部のページだけオンデマンドにしたいなら、上のように export const prerender = false; を書く
  • serverモード … 既定は false。全体がオンデマンドなので、この記述自体が不要

もうひとつ、オンデマンドのルートには制限があります。使える rest parameter は1つだけです。
SSRに寄せる予定があるなら、ルートの形を決める段階で頭に置いておくとよさそうです。

なお、オンデマンドレンダリングを使うにはアダプターの導入が必要になります。個人ブログのように中身が更新のたびにしか変わらないサイトなら、素直に静的のままでいいと思います。

まとめ|動的ルーティングは「作るURLを先に決める」仕組み

Astroの動的ルーティングは、覚えることが多そうに見えて、実は骨格がひとつしかありません。
ファイル名の [ ] で穴を空けて、その穴に入る値の一覧を getStaticPaths() で渡す。これだけです。

[...rest] も paginate() も、この骨格の上に乗っている応用にすぎません。
このブログの記事ページも [...slug].astro という動的ルート1枚で作られていて、記事が増えてもファイルは増えない構成になっています。

この記事のまとめ
  • src/pages のファイルパスがそのままURLになる。ファイル名を [ ] で囲むと動的ルート
  • 静的モードの動的ルートでは getStaticPaths() が必須。返すのは作るURLの一覧
  • params がURLを決め、props はURLに影響せず表示用のデータだけを渡す
  • [...rest] は任意の深さにマッチ。undefined でトップ階層も作れて、値は自動デコードされない
  • 一覧の分割は paginate() に任せる。SSRに切り替えると getStaticPaths() は不要になる
らるじゅらるじゅ

最初のうちは getStaticPaths() のエラーが煩わしく感じるかもしれません。
でもここを理解すると、ページの作り方をURL単位で設計できるようになります。ぜひ手元で1枚作ってみてください!