Dev Study
← 解説「動的ルート — [id] でURLの一部を受け取る」に戻る

サンプルコードで身につける: 動的ルート — [id] でURLの一部を受け取る

解説で学んだ概念を、5つの具体例で確認します。素朴な例から実務寄りの例へと進みます。

1最小の [id] ページ

動的ルートの最小形で、URL の [id] 部分の実際の値を params から受け取って表示するだけの例です。params は Promise なので await で取り出す、という受け取り方の型もここで覚えてしまいましょう。

// app/items/[id]/page.tsx → /items/1 /items/2 ... すべてにマッチ
export default async function ItemPage({
  params,
}: {
  params: Promise<{ id: string }>;
}) {
  const { id } = await params; // [id] 部分の実際の値
  return <h1>アイテム {id} のページ</h1>;
}

// /items/1 なら id は "1"、/items/abc なら id は "abc"。
// 値は常に文字列として渡ってくる点に注意。

2ブログ記事の動的ルート

受け取った id でデータを探して表示する、動的ルートの定番の流れです。記事が何百件あってもファイルはこの1つだけで済む、という「同じレイアウトで中身だけ違うページ」の作り方そのものです。

// app/blog/[id]/page.tsx
const posts = [
  { id: "1", title: "はじめての投稿", body: "こんにちは。" },
  { id: "2", title: "Next.js 入門", body: "App Router を学ぶ。" },
];

export default async function PostPage({
  params,
}: {
  params: Promise<{ id: string }>;
}) {
  const { id } = await params;
  const post = posts.find((p) => p.id === id); // id で記事を探す
  if (!post) {
    return <p>記事が見つかりません</p>;
  }
  return (
    <article>
      <h1>{post.title}</h1>
      <p>{post.body}</p>
    </article>
  );
}

31つのパターンが無数のURLを受け止める

app/blog/[id]/page.tsx という1ファイルがあらゆる /blog/◯◯ を処理する様子を、アクセスログの振り分けとして実行できる形にした例です。固定部分(blog)は一致が必要で、[id] 部分は何でも受け取る、という照合の感覚をつかめます。

TypeScript

4カテゴリと商品IDの2段の動的ルート

動的セグメントは1つの URL に複数置けることを示す例です。EC サイトの /products/カテゴリ/商品ID のような階層的な URL も、フォルダを2段にするだけで params にまとめて入ってきます。

// app/products/[category]/[id]/page.tsx
// → /products/books/123 や /products/games/9 にマッチ
export default async function ProductPage({
  params,
}: {
  params: Promise<{ category: string; id: string }>;
}) {
  const { category, id } = await params;
  return (
    <main>
      <p>カテゴリ: {category}</p>
      <p>商品ID: {id}</p>
    </main>
  );
}
// /products/books/123 → category は "books"、id は "123"

5ユーザープロフィールページ

SNS の /users/ユーザー名 のように、ID 以外の文字列でも動的ルートが使える実務的な例です。一覧ページから各プロフィールへ Link でつなぐ構成は、記事・商品・ユーザーなどあらゆる「一覧と詳細」の原型になります。

// app/users/[username]/page.tsx → /users/kazu など
import Link from "next/link";

const users = [
  { username: "kazu", bio: "Web開発を勉強中" },
  { username: "mio", bio: "デザイナー" },
];

export default async function UserPage({
  params,
}: {
  params: Promise<{ username: string }>;
}) {
  const { username } = await params;
  const user = users.find((u) => u.username === username);
  if (!user) return <p>ユーザーが見つかりません</p>;
  return (
    <main>
      <h1>@{user.username}</h1>
      <p>{user.bio}</p>
      <Link href="/users">ユーザー一覧に戻る</Link>
    </main>
  );
}