本文へ移動
verified onnext@16.3.5react@19
最終検証日 2026-09-26
Next.js道場

動的ルーティング。角括弧の3つの形

動的ルーティングは、フォルダ名を角括弧で囲むとその部分が変数になる仕組みで、1つぶんを受ける形、1つ以上を受ける形、0個でもよい形の3つがあります。

角括弧は3つの形がある

フォルダ名を角括弧で囲むと、その部分が変数になります。囲み方で受けられる範囲が変わります。

実機で確かめた当たり方。/docs と /help の違いに注目
書き方受ける数そのフォルダ自身
[id]ちょうど1つ当たらない
[...slug]1つ以上当たらない
[[...slug]]0個でもよい当たる

3行目だけが特別です。app/help/[[...slug]]/page.tsx は /help そのものにも当たります。実機で確かめたビルドの出力です。

├   /docs/[...slug]
│ ├ ● /docs/a
│ └ ● /docs/a/b          ← /docs 自体は無い
└   /help/[[...slug]]
  ├ ● /help              ← ここが出る
  └ ● /help/x

名前は URL に出ない

角括弧の中の名前は、受け取るときの変数名でしかありません。URL のどこにも現れません。

だから、同じ深さに違う名前で2つ置くと止まります。実機の文言です。

Ambiguous route pattern "/blog/[*]" matches multiple routes:
  - /blog/[id]
  - /blog/[slug]

/blog/abc が来たとき、それが id なのか slug なのかを決める手がかりが URL にありません。

分けたいなら、動的でない部分を挟む
止まる
app/blog/[slug]/page.tsx
app/blog/[id]/page.tsx

どちらも /blog の次の1つに当たる。

通る
app/blog/[slug]/page.tsx
app/blog/edit/[id]/page.tsx

edit という固定の部分が、当たり先を決めてくれる。

深さが違えば共存できます。/blog/[slug] と /blog/[id]/edit は通ります。止まるのは同じ深さのときだけです。

params は await して受け取る

export default async function Page({
  params,
}: {
  params: Promise<{ slug: string }>
}) {
  const { slug } = await params
  return <p>{slug}</p>
}

params は Promise です。同期のまま使えた頃のコード例が残っているので、古い記事を写すときは注意してください。await せずに読むと、本番では何も言われないまま undefined になります(params が undefined になる)。

事前に描かれるかどうかは、一覧を渡したかで決まる

動的なパスは、そのままでは毎回サーバーで描く側です。どの値が来るか分からないからです。

実機で確かめました。generateStaticParams を書かないページは、ビルドの成果物にその画面の文字が1件も入りません。

export async function generateStaticParams() {
  return [{ slug: 'a' }, { slug: 'b' }]
}

これを書くと、渡した数だけ事前に描かれます。output: 'export' で静的なファイルだけを書き出すときは、この関数が必須です(generateStaticParams が無いエラー)。

├   /blog/[slug]
│ ├ ● /blog/a
│ └ ● /blog/b
└ ƒ /news/[id]          ← 渡していないほう
渡すか渡さないかの目安
中身数渡すか
記事・商品・ドキュメント決まっている渡す
検索結果・絞り込み決まらない渡さない
件数が非常に多いもの多いよく見られるぶんだけ渡す

渡していないパスへのアクセスも、既定では 404 になりません。その場で作られて返り、次からは出来あがったものが使われます。この挙動はキャッシュの考え方と地続きです。

無いものは、無いと言う

一覧を渡す渡さないに関係なく、そのデータが存在しないときの扱いは自分で書きます。

const { id } = await params
const item = await findItem(id)
if (!item) notFound()

書かないと、名前も本文も空のページが 200 で返り、クローラにも拾われます。実際に動いているものが404 とエラーの最小構成にあります。

まとめ

  1. 角括弧は1つぶん / 1つ以上 / 0個でもよいの3つ。そのフォルダ自身に当たるのは3つ目だけ
  2. 角括弧の中の名前はURL に出ない。同じ深さに2つ置くと止まる
  3. params は await して受け取る
  4. 一覧を渡した数だけ事前に描かれる。渡さなければ毎回描く
  5. 無いときは notFound()。書かないと空のページが 200 で返る

出典

このページの内容は上記の一次情報を基準に書いています。個人ブログや二次情報を根拠にしていません。

理解できたか試す

では、このコードはどうなりますか。

記事を slug で引くページがある場所に、id で引くページも足しました。どちらも /blog の直下です。ビルドするとどうなりますか。

この問題を解くこのテーマの問題 3 問