【Next.js】ルートグループ・動的ルートの書き方

JavaScript

こんにちは、かつコーチです。

前回までで、page.tsx によるファイルベースルーティングと、layout.tsx を使った共通レイアウトの作り方を解説してきました。

ここまでは「固定のURL」しか扱ってきませんでしたが、実際のアプリでは /products/123 のように、IDによってページ内容が変わるURLが必要になります。

今回は、そのための動的ルートと、URL構造を整理するためのルートグループを解説します。

動的ルート・ルートグループとは?

動的ルートとは

動的ルート(Dynamic Route)とは、フォルダ名を [id] のように角括弧で囲むことで、URLの一部を変数として受け取れる仕組みです。

app/products/[id]/page.tsx というファイルを作ると、/products/1/products/next-js など、あらゆる値にマッチします。

ルートグループとは

ルートグループ(Route Group)とは、フォルダ名を (marketing) のように丸括弧で囲むことで、URLには影響を与えずにファイルを整理できる仕組みです。

app/(marketing)/about/page.tsx を作っても、URLは /marketing/about にはならず /about のままになります。

なぜそれが必要なのか

動的ルートがないと、商品数やユーザー数だけ page.tsx を手動で作る必要があり、現実的ではありません。

一方ルートグループは、URLの階層とファイルの階層を一致させる必要がなくなるため、「会員向けページ」「マーケティングページ」のようにレイアウトごとにフォルダを分けたいときに役立ちます。

基本の書き方

手順1:動的ルートを作る

// app/products/[id]/page.tsx
export default async function ProductPage({
  params,
}: {
  params: Promise<{ id: string }>;
}) {
  const { id } = await params;

  return <h1>商品ID: {id}</h1>;
}

params はURLの動的な部分を持つオブジェクトです。

Next.js 15以降、paramsPromiseとして渡されるため、await してから中身を取り出す必要があります。

手順2:複数階層をまとめて受け取る(Catch-allルート)

URLの階層数が可変の場合は、[...slug] というCatch-allルートを使います。

// app/docs/[...slug]/page.tsx
export default async function DocsPage({
  params,
}: {
  params: Promise<{ slug: string[] }>;
}) {
  const { slug } = await params;
  // /docs/a/b/c なら slug は ["a", "b", "c"]

  return <p>パス: {slug.join(" / ")}</p>;
}

/docs 自体にもマッチさせたい場合は、[[...slug]] のように二重角括弧のオプショナルCatch-allを使います。

手順3:ルートグループでフォルダを整理する

app/
├── (marketing)/
│   ├── layout.tsx   ← マーケティング用の共通レイアウト
│   ├── about/page.tsx
│   └── pricing/page.tsx
└── (app)/
    ├── layout.tsx   ← アプリ用の共通レイアウト
    └── dashboard/page.tsx

(marketing)(app) はそれぞれ別の layout.tsx を持てるため、URLは変えずにヘッダーやナビゲーションだけを切り替えられます。

つまずきやすい設定・注意点

動的ルートのフォルダ名は、同じ階層で1種類しか使えません。

app/products/[id]/page.tsxapp/products/[slug]/page.tsx を同時に作るとビルドエラーになるので注意してください。

よくあるつまずきポイント・エラー対処

一次情報:paramsをそのまま同期的に使ってハマった話

私がNext.js 15で動的ルートを書いたとき、以前のバージョンの書き方のまま params.id に直接アクセスしてTypeScriptエラーに悩んだことがあります。

❌ Before:paramsを同期的なオブジェクトとして扱う

// これは古いバージョンの書き方(Next.js 15以降ではエラーになる)
export default function ProductPage({
  params,
}: {
  params: { id: string };
}) {
  return <h1>商品ID: {params.id}</h1>;
}

このコードは型エラーにはならないこともありますが、実行時に params.idundefined 扱いになり、コンソールに警告が出て気づきました。

エラーメッセージは「params should be awaited before using its properties」というもので、原因を調べてようやく params の仕様変更に気づきました。

✅ After:Promiseとしてawaitしてから使う

export default async function ProductPage({
  params,
}: {
  params: Promise<{ id: string }>;
}) {
  const { id } = await params;

  return <h1>商品ID: {id}</h1>;
}

コンポーネントを async function にし、paramsawait してから使うことで解決しました。

Next.js のバージョンによって params の扱いが変わるため、公式ドキュメントやエラーメッセージをよく確認する癖をつけておくと安心です。

まとめ

この記事のポイント

  • 動的ルートは [id] のようにフォルダ名を角括弧で囲んで作る
  • 複数階層をまとめて受け取るには [...slug](Catch-all)を使う
  • ルートグループ (folderName) はURLに影響を与えずファイルを整理できる
  • Next.js 15以降、params はPromiseなので await してから使う必要がある

次に読むべき記事

動的ルートが書けるようになったら、次は「そのページが表示されるまでの間、どう見せるか」を整えていきましょう。

次回は、loading.tsxerror.tsxnot-found.tsx を使ったUXの整え方を解説します。

→ 次の記事:loading.tsx・error.tsx・not-found.tsxでUXを整える

タイトルとURLをコピーしました