こんにちは、かつコーチです。
前回までで、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以降、params はPromiseとして渡されるため、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.tsx と app/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.id が undefined 扱いになり、コンソールに警告が出て気づきました。
エラーメッセージは「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 にし、params を await してから使うことで解決しました。
Next.js のバージョンによって params の扱いが変わるため、公式ドキュメントやエラーメッセージをよく確認する癖をつけておくと安心です。
まとめ
この記事のポイント
- 動的ルートは
[id]のようにフォルダ名を角括弧で囲んで作る - 複数階層をまとめて受け取るには
[...slug](Catch-all)を使う - ルートグループ
(folderName)はURLに影響を与えずファイルを整理できる - Next.js 15以降、
paramsはPromiseなのでawaitしてから使う必要がある
次に読むべき記事
動的ルートが書けるようになったら、次は「そのページが表示されるまでの間、どう見せるか」を整えていきましょう。
次回は、loading.tsx・error.tsx・not-found.tsx を使ったUXの整え方を解説します。
→ 次の記事:loading.tsx・error.tsx・not-found.tsxでUXを整える