動的ルーティング — [slug]・paramsとgenerateStaticParamsでページを量産する
ブログ記事、商品ページ、ユーザープロフィール——「URLの一部だけが違う、同じ形のページ」は無数にあります。これらを1ページずつ手で作るのは現実的ではありません。Next.jsの動的ルーティングを使えば、1つのファイルで何百・何千ページ分をまかなえます。
この章では、[slug] という動的セグメントの作り方、params の受け取り方、そして静的生成(SSG)につなぐ generateStaticParams までを押さえます。
学習者記事が100本あったら、page.tsx を100個作るの…?さすがに無理だよね。
動的セグメント — フォルダ名を [ ] で囲む
URLの一部を変数として扱いたいときは、フォルダ名を角かっこ [ ] で囲みます。これを動的セグメントと呼びます。
app/
└── blog/
└── [slug]/
└── page.tsx → /blog/任意の文字列
この1ファイルで、/blog/hello・/blog/nextjs-tips・/blog/2026-summary などすべてに対応できます。[slug] の部分(hello など)は params 経由で受け取れます。

params の受け取り方(Next.js 15以降)
動的セグメントの値は、ページコンポーネントの params から取り出します。**Next.js 15以降、params は Promise(非同期)**になったため、await して受け取ります。
props の構造:
| プロパティ | 型 | 説明 |
|---|---|---|
params | Promise<{ [key]: string }> | 動的セグメントの値。[slug] なら { slug: "..." } |
searchParams | Promise<{ [key]: string }> | ?key=value のクエリ文字列(詳細は第7章) |
// src/app/blog/[slug]/page.tsx
export default async function BlogPostPage({
params,
}: {
params: Promise<{ slug: string }>;
}) {
const { slug } = await params; // ← await で取り出す
return <h1>記事: {slug}</h1>;
}
先生フォルダ名 [slug] の「slug」が、そのまま params.slug のキー名になる。[id] なら params.id、[category] なら params.category だよ。
実例 — IDから記事を取得して表示
params で受け取ったIDを使って、データを取得するのが典型的な流れです。データ取得(fetch)は第7章で詳しく扱います。
// src/app/blog/[slug]/page.tsx
import { notFound } from "next/navigation";
export default async function BlogPostPage({
params,
}: {
params: Promise<{ slug: string }>;
}) {
const { slug } = await params;
const res = await fetch(`https://api.example.com/posts/${slug}`);
if (!res.ok) {
notFound(); // 記事が無ければ404へ(第8章で解説)
}
const post = await res.json();
return (
<article>
<h1>{post.title}</h1>
<p>{post.body}</p>
</article>
);
}generateStaticParams — ビルド時にページを静的生成する
動的ルーティングは、デフォルトではアクセスのたびにサーバーでページを生成します。しかしブログ記事のように内容が頻繁に変わらないページは、ビルド時にあらかじめHTMLを作っておく(静的生成・SSG)方が高速で、SEOにも有利です。
そのために使うのが generateStaticParams です。「どんな params の組み合わせを事前に生成すべきか」を返します。
戻り値: params オブジェクトの配列(各要素が1ページに対応)
// src/app/blog/[slug]/page.tsx
// ビルド時に「どのslugを生成するか」を返す
export async function generateStaticParams() {
const posts = await fetch("https://api.example.com/posts").then((r) => r.json());
return posts.map((post: { slug: string }) => ({
slug: post.slug, // { slug: "hello" }, { slug: "nextjs-tips" }, ...
}));
}
export default async function BlogPostPage({
params,
}: {
params: Promise<{ slug: string }>;
}) {
const { slug } = await params;
// ...
}これで、ビルド時に全記事のHTMLが生成され、/blog/hello などへ即座に応答できるようになります。
catch-all セグメント — 複数階層をまとめて受け取る
[slug] は1階層分ですが、/docs/a/b/c のように階層数が決まっていないURLを1ファイルで受けたいこともあります。その場合は [...slug](catch-all)を使います。
| 書き方 | マッチするURL | params |
|---|---|---|
[slug] | /docs/a | { slug: "a" } |
[...slug] | /docs/a/b/c | { slug: ["a", "b", "c"] }(配列) |
[[...slug]] | /docs も /docs/a/b も | 省略時は空。任意の階層 |
// src/app/docs/[...slug]/page.tsx
export default async function DocsPage({
params,
}: {
params: Promise<{ slug: string[] }>;
}) {
const { slug } = await params; // ["guide", "intro"] など配列で届く
return <p>パス: {slug.join(" / ")}</p>;
}まとめ
- フォルダ名を
[slug]のように[ ]で囲むと動的セグメントになり、1ファイルで多数のURLに対応できる - 値は
paramsから取り出す。Next.js 15以降はawait params(Promise) - フォルダ名(
[id]のid)がそのままparamsのキー名になる generateStaticParamsで、ビルド時にどのページを静的生成するかを指定できる(SSG)- 階層数が不定のURLは
[...slug](catch-all)で配列として受け取る
次の章では、ここで何度か登場した データ取得とキャッシュ を本格的に扱います。fetch の結果をどうキャッシュし、いつ再取得するか——Next.jsならではの仕組みを理解しましょう。