言語を切り替える
テーマを切り替える

Next.js 動的ルーティングとパラメータ処理の完全ガイド:入門から型安全まで

先週、Next.js プロジェクトのリファクタリング中に、ドキュメントどおりに書いたはずの動的ルートが 404 になる問題に直面しました。コンソールには何も出ず、途方に暮れました。原因は Next.js 14 の App Router でルートパラメータの取得方法が変わったのに、Pages Router の古い書き方を使っていたことでした。

Next.js のルーティングでつまずくのは、これが初めてではありません。getStaticPaths から generateStaticParams への移行のたびに、仕様を学び直す必要があります。動的ルート、catch-all、オプショナルパラメータ——概念がごちゃ混ぜになると、本当に混乱します。

Next.js の動的ルーティングに戸惑っている方、Pages Router から App Router へ移行中の方に向けた記事です。基礎の動的ルートから型安全な実装まで、実践的なコード例で整理します。

読み終えると、シーンごとのルート設計、正しいパラメータ取得、TypeScript による型付けが一通り身につきます。虚論ではなく、すぐ使えるコードと解決策です。さっそく始めましょう。

第 1 章:動的ルーティングの基礎(いちばんシンプルなところから)

動的ルートとは?

よくあるのがブログです。記事 URL が /blog/記事ID のとき、記事ごとにページファイルを作るのは現実的ではありません。動的ルートなら、1 つのページで記事詳細をまとめて扱えます。

Next.js App Router では、角括弧付きのフォルダ名で動的ルートを作ります。例を見ると早いです。

app/
├── blog/
│   └── [slug]/
│       └── page.tsx    ← これが動的ルート

この構造は /blog/* にマッチします。例えば:

  • /blog/hello-worldslug = "hello-world"
  • /blog/nextjs-guideslug = "nextjs-guide"
  • /blog/123slug = "123"

いちばんシンプルな動的ルート

app/blog/[slug]/page.tsx を作成し、次のコードを書きます。

// app/blog/[slug]/page.tsx
export default function BlogPost({
  params
}: {
  params: { slug: string }
}) {
  return (
    <div>
      <h1>記事詳細</h1>
      <p>現在の slug: {params.slug}</p>
    </div>
  )
}

これだけです。/blog/hello-world にアクセスすると、params.slug"hello-world" になります。

初心者が陥りやすいミス

  1. ❌ ファイル名を [slug].tsx にする(App Router ではフォルダが必要)
  2. props.slug を直接参照する(params 経由で取得)
  3. ❌ フォルダ名の角括弧を忘れる(角括弧がないと静的ルート)

Pages Router と App Router の比較

Pages Router を使ったことがあると違和感があるかもしれません。以前は pages/blog/[slug].tsx でしたが、App Router ではかなり変わっています。

項目Pages RouterApp Router
ファイル位置pages/blog/[slug].tsxapp/blog/[slug]/page.tsx
パラメータ取得router.query.slug または getStaticPropsparams.slug
型定義手動定義が多いprops 型から推論しやすい
静的生成getStaticPathsgenerateStaticParams

移行時にいちばん慣れないのがパラメータ取得です。Pages Router では useRouter が使えましたが、App Router の Server Components では hooks が使えず、params prop だけです。Server Components はデフォルトでサーバー側レンダリングのため、クライアントの router オブジェクトがありません。

実践例:EC の商品詳細ページ

商品詳細の URL が /products/商品ID の場合の実装です。

// app/products/[id]/page.tsx
interface Product {
  id: string
  name: string
  price: number
  description: string
}

// DB 取得のモック
async function getProduct(id: string): Promise<Product | null> {
  const products: Product[] = [
    { id: '1', name: 'TypeScript 入門書', price: 99, description: '初心者向け' },
    { id: '2', name: 'React 実践ガイド', price: 129, description: 'ゼロから本番まで' }
  ]
  return products.find(p => p.id === id) || null
}

export default async function ProductPage({
  params
}: {
  params: { id: string }
}) {
  const product = await getProduct(params.id)

  if (!product) {
    return <div>商品が存在しません</div>
  }

  return (
    <div>
      <h1>{product.name}</h1>
      <p className="price">¥{product.price}</p>
      <p>{product.description}</p>
    </div>
  )
}

押さえるポイント

  1. コンポーネントを async にしている(Server Components は非同期可)
  2. 先にデータ取得し、結果に応じて描画を分岐
  3. 商品がない場合を処理(404 相当)

本物の 404 ページにしたい場合は、notFound を使います。

import { notFound } from 'next/navigation'

export default async function ProductPage({
  params
}: {
  params: { id: string }
}) {
  const product = await getProduct(params.id)

  if (!product) {
    notFound() // 404 ページへ
  }

  return (
    <div>
      <h1>{product.name}</h1>
      {/* ... */}
    </div>
  )
}

存在しない商品へのアクセスでは、カスタムの not-found.tsx が表示され、体験が良くなります。

ここまでで基礎の動的ルートは押さえました。次は、階層の深いパスを扱う方法です。

第 2 章:Catch-All ルートとオプショナルパラメータ(複雑なパス)

Catch-All が必要なとき

ドキュメントサイトを想定します。URL は次のようになります。

  • /docs/getting-started
  • /docs/api/authentication
  • /docs/api/database/queries
  • /docs/guides/deployment/vercel

階層が 2 段でも 3 段以上でもあり得ます。通常の動的ルートでは足りず、Catch-All ルートが必要です。

Catch-All ルート:[...slug]

フォルダ名は [...slug](3 つのドット)で、任意の深さのパスにマッチします。

app/
├── docs/
│   └── [...slug]/
│       └── page.tsx    ← /docs/* 以下すべてにマッチ

マッチ例:

  • /docs/getting-startedslug = ["getting-started"]
  • /docs/api/authenticationslug = ["api", "authentication"]
  • /docs/guides/deployment/vercelslug = ["guides", "deployment", "vercel"]

注意slug配列であり、文字列ではありません。

コード実装:ドキュメントシステム

// app/docs/[...slug]/page.tsx
interface Doc {
  title: string
  content: string
}

async function getDoc(slugArray: string[]): Promise<Doc | null> {
  const path = slugArray.join('/')

  const docs: Record<string, Doc> = {
    'getting-started': {
      title: 'クイックスタート',
      content: '製品へようこそ...'
    },
    'api/authentication': {
      title: 'API 認証',
      content: 'JWT で認証します...'
    },
    'api/database/queries': {
      title: 'データベースクエリ',
      content: 'Prisma でクエリします...'
    }
  }

  return docs[path] || null
}

export default async function DocsPage({
  params
}: {
  params: { slug: string[] }
}) {
  const doc = await getDoc(params.slug)

  if (!doc) {
    return <div>ドキュメントが存在しません</div>
  }

  return (
    <article>
      <h1>{doc.title}</h1>
      <div dangerouslySetInnerHTML={{ __html: doc.content }} />

      <nav>
        <a href="/docs">ドキュメント</a>
        {params.slug.map((segment, i) => {
          const href = `/docs/${params.slug.slice(0, i + 1).join('/')}`
          return (
            <span key={i}>
              {' / '}
              <a href={href}>{segment}</a>
            </span>
          )
        })}
      </nav>
    </article>
  )
}

このコードのポイント

  1. slugArray.join('/') でパス配列を文字列化
  2. slice でパス接頭辞を切り出し、パンくずを実装
  3. params: { slug: string[] } で TypeScript が型チェック

オプショナル Catch-All:[[...slug]]

/docs/docs/*マッチさせたいときは、オプショナル Catch-All を使います。通常の Catch-All は /docs(パラメータなし)にはマッチしません。

app/
├── docs/
│   └── [[...slug]]/
│       └── page.tsx    ← 二重の角括弧に注意

マッチ例:

  • /docsslug = undefined
  • /docs/getting-startedslug = ["getting-started"]
  • /docs/api/authslug = ["api", "auth"]

コードでは slugundefined になり得ることを処理します。

// app/docs/[[...slug]]/page.tsx
export default async function DocsPage({
  params
}: {
  params: { slug?: string[] }
}) {
  if (!params.slug) {
    return <div>ドキュメントセンターへようこそ</div>
  }

  const doc = await getDoc(params.slug)
  // ...
}

初心者が踏みやすい落とし穴

落とし穴 1:slug が配列だと忘れる

// ❌ 誤り
<h1>現在のパス: {params.slug}</h1>  // "api,authentication" のように表示される

// ✅ 正しい
<h1>現在のパス: {params.slug.join('/')}</h1>  // "api/authentication"

落とし穴 2:静的生成時のデータ形式

// ❌ 誤り
export function generateStaticParams() {
  return [
    { slug: 'api/auth' }  // 文字列。配列ではない!
  ]
}

// ✅ 正しい
export function generateStaticParams() {
  return [
    { slug: ['api', 'auth'] }
  ]
}

落とし穴 3:3 種類のルートの混同

ルート種別フォルダ名マッチ範囲パラメータ型
動的ルート[slug]/blog/123string
Catch-All[...slug]/docs/a/b/c/docs は含まない)string[]
オプショナル Catch-All[[...slug]]/docs/docs/a/b/cstring[] | undefined

フォルダ名を間違えると、たまに開いてたまに 404、という状態になりがちです。

実践テクニック:特殊文字の扱い

URL に日本語や特殊文字がある場合は、デコードを忘れないでください。

export default async function Page({
  params
}: {
  params: { slug: string[] }
}) {
  const decodedSlug = params.slug.map(s => decodeURIComponent(s))

  console.log(params.slug)      // ["api", "%E8%AE%A4%E8%AF%81"]
  console.log(decodedSlug)      // ["api", "認証"]

  // ...
}

複雑なパス構造はこれで扱えます。次は、動的ページをいつ生成するか——generateStaticParams です。

第 3 章:generateStaticParams の深掘り(いつ・どう使うか)

なぜ generateStaticParams が必要か

ブログに 100 記事あり、各記事が /blog/[slug] の動的ルートだとします。最適化しないと、訪問のたびに DB クエリ → SSR → 返却、となり遅くなります。ビルド時に全記事ページを事前レンダリングするのが generateStaticParams の役割です。

基本:ブログ記事の静的生成

// app/blog/[slug]/page.tsx
interface Post {
  slug: string
  title: string
  content: string
}

export async function generateStaticParams() {
  const posts = await fetch('https://api.example.com/posts').then(r => r.json())

  return posts.map((post: Post) => ({
    slug: post.slug
  }))
}

export default async function BlogPost({
  params
}: {
  params: { slug: string }
}) {
  const post = await fetch(`https://api.example.com/posts/${params.slug}`)
    .then(r => r.json())

  return (
    <article>
      <h1>{post.title}</h1>
      <div dangerouslySetInnerHTML={{ __html: post.content }} />
    </article>
  )
}

流れ

  1. ビルド時に generateStaticParams が全 slug を返す
  2. Next.js が各 slug 用の静的 HTML を生成
  3. アクセス時は静的ファイルを返すため高速

ビルド後の例

.next/server/app/blog/
├── hello-world.html
├── nextjs-guide.html
└── typescript-tips.html

いつ使うべきか

向いている場面

  • ブログ・ニュース詳細(内容が比較的固定)
  • 商品詳細(件数が限定的、例:1 万件未満)
  • ドキュメント・ヘルプセンター
  • ユーザー数が少ないプロフィールページ

向かない場面

  • 検索結果(パラメータ組み合わせが無限)
  • リアルタイムデータ(株価・スコアなど)
  • UGC が膨大なプラットフォーム
  • ログイン状態で表示が変わるページ

応用 1:Catch-All の静的生成

[...slug] では、返すパラメータは配列です。

// app/docs/[...slug]/page.tsx
export async function generateStaticParams() {
  const docPaths = [
    ['getting-started'],
    ['api', 'authentication'],
    ['api', 'database', 'queries'],
    ['guides', 'deployment', 'vercel']
  ]

  return docPaths.map(slug => ({ slug }))
}

export default async function DocsPage({
  params
}: {
  params: { slug: string[] }
}) {
  // ...
}

注意{ slug: ['api', 'auth'] } であり、{ slug: 'api/auth' } ではありません。

応用 2:複数パラメータ

/shop/[category]/[productId] のようなルートでは:

app/
├── shop/
│   └── [category]/
│       └── [productId]/
│           └── page.tsx

generateStaticParams は次のように書きます。

// app/shop/[category]/[productId]/page.tsx
export async function generateStaticParams() {
  const products = [
    { category: 'electronics', productId: 'iphone-15' },
    { category: 'electronics', productId: 'macbook-pro' },
    { category: 'books', productId: 'clean-code' },
    { category: 'books', productId: 'refactoring' }
  ]

  return products.map(p => ({
    category: p.category,
    productId: p.productId
  }))
}

export default async function ProductPage({
  params
}: {
  params: { category: string; productId: string }
}) {
  return (
    <div>
      <h1>カテゴリ: {params.category}</h1>
      <p>商品 ID: {params.productId}</p>
    </div>
  )
}

応用 3:オンデマンド生成(fallback 相当)

記事が 10 万件あるとき、全部を事前生成するのは現実的ではありません。人気記事だけ事前生成し、残りは初回アクセス時に生成します。

// app/blog/[slug]/page.tsx
export const dynamicParams = true

export async function generateStaticParams() {
  const topPosts = await fetchTopPosts(100)

  return topPosts.map(post => ({
    slug: post.slug
  }))
}

export default async function BlogPost({
  params
}: {
  params: { slug: string }
}) {
  const post = await fetchPost(params.slug)

  if (!post) {
    notFound()
  }

  return <article>{/* ... */}</article>
}

dynamicParams = true のとき:

  • 事前生成済み:即座に返却(最速)
  • 未生成:初回リクエストで生成し、以降はキャッシュ
  • 存在しない:404

つまずきやすい点

Q1:generateStaticParams はいつ動く?

ビルド時npm run build)のみです。開発(npm run dev)では静的生成の成果物は見えません。ビルド後に確認してください。

Q2:データが更新されたら?

静的生成後は内容が固定されます。更新時は再ビルド・再デプロイが必要です。対策として ISR、dynamicParams = truerevalidate などがあります。

export const revalidate = 60

export default async function Page() {
  // ...
}

Q3:ビルドが遅くなる

返すパスが多いほどビルド時間が伸びます。対策:

  • 事前生成するページ数を減らす(人気記事のみ)
  • インクリメンタルビルド(Vercel / Netlify)
  • オンデマンド生成(dynamicParams = true

第 4 章:ルートパラメータの型安全(any からの卒業)

なぜ型安全が必要か

export default async function Page({
  params
}: {
  params: { slug: string }
}) {
  const id = parseInt(params.slug)

  if (isNaN(id)) {
    return <div>無効な ID</div>
  }

  // ...
}

params.slugstring 型ですが、実際には数値 ID が欲しい——この不一致はコンパイルでは検出されず、実行時に初めてわかります。

基本的な型制約

App Router の params はデフォルトで string または string[] です。カスタム型で制約を強められます。

// app/blog/[slug]/page.tsx
interface BlogParams {
  slug: string
}

export default async function BlogPost({
  params
}: {
  params: BlogParams
}) {
  const post = await fetchPost(params.slug)
  // ...
}

パラメータが増えると効いてきます。

// app/shop/[category]/[productId]/page.tsx
interface ShopParams {
  category: 'electronics' | 'books' | 'clothing'
  productId: string
}

export default async function ProductPage({
  params
}: {
  params: ShopParams
}) {
  if (params.category === 'toys') {  // ❌ コンパイルエラー
    // ...
  }
}

ランタイム検証:Zod との組み合わせ

型はコンパイル時のみ。実行時に不正値が来ることもあるため、Zod で検証するのが安全です。

npm install zod
// app/products/[id]/page.tsx
import { z } from 'zod'
import { notFound } from 'next/navigation'

const paramsSchema = z.object({
  id: z.string().regex(/^\d+$/, '数字 ID である必要があります')
})

export default async function ProductPage({
  params
}: {
  params: { id: string }
}) {
  const result = paramsSchema.safeParse(params)

  if (!result.success) {
    notFound()
  }

  const { id } = result.data
  const product = await fetchProduct(parseInt(id))
  // ...
}

メリット:

  • コンパイル時の型チェック
  • 実行時のフォーマット検証
  • 不正リクエストを 404 で弾き、DB 負荷を減らせる

応用:型安全な generateStaticParams

戻り値にも型を付けられます。

// app/blog/[slug]/page.tsx
interface BlogParams {
  slug: string
}

export async function generateStaticParams(): Promise<BlogParams[]> {
  const posts = await fetchAllPosts()

  return posts.map(post => ({
    slug: post.slug
  }))
}

export default async function BlogPost({
  params
}: {
  params: BlogParams
}) {
  // ...
}

実践例:多言語ブログ

URL が /[locale]/blog/[slug] の場合(例:/zh/blog/hello-world):

// app/[locale]/blog/[slug]/page.tsx
import { z } from 'zod'
import { notFound } from 'next/navigation'

const locales = ['zh', 'en', 'ja'] as const
type Locale = typeof locales[number]

interface PageParams {
  locale: Locale
  slug: string
}

const paramsSchema = z.object({
  locale: z.enum(locales),
  slug: z.string().min(1)
})

export async function generateStaticParams(): Promise<PageParams[]> {
  const posts = await fetchAllPosts()

  return locales.flatMap(locale =>
    posts.map(post => ({
      locale,
      slug: post.slug
    }))
  )
}

export default async function BlogPost({
  params
}: {
  params: PageParams
}) {
  const result = paramsSchema.safeParse(params)
  if (!result.success) {
    notFound()
  }

  const { locale, slug } = result.data
  const post = await fetchPost(slug, locale)

  if (!post) {
    notFound()
  }

  return (
    <article>
      <h1>{post.title}</h1>
      <div>{post.content}</div>
    </article>
  )
}

この実装の強み

  1. Locale"zh" | "en" | "ja" に限定
  2. generateStaticParams の戻り値が PageParams[] で構造を保証
  3. Zod でランタイム検証
  4. 型定義から実行時まで一貫

よくある型の問題

Q1:params が Promise<...> のとき

Next.js 15 以降では params が非同期のことがあります。

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

Next.js 14 で同期版を使う場合:

export default async function Page({
  params
}: {
  params: { slug: string }
}) {
  // そのまま利用
}

Q2:型ヒントが any になる

確認項目:

  1. tsconfig.json で strict が有効か
  2. Next.js の型を正しく import しているか
  3. ファイル名が page.tsx

Q3:Zod 失敗時の詳細を見たい

const result = paramsSchema.safeParse(params)

if (!result.success) {
  console.error('パラメータ検証失敗:', result.error.format())
  notFound()
}

型安全チェックリスト

  • 動的ルートページすべてで params 型を定義した
  • generateStaticParams の戻り値型が params と一致している
  • 重要ルートで Zod 等のランタイム検証を使っている
  • TypeScript strict モードを有効にしている
  • 複雑なパラメータにユニオン型・リテラル型を使っている

まとめ

ここまで読めば、Next.js 動的ルーティングの全体像は押さえたはずです。

基礎[slug] で単層パス、params の取得
Catch-All[...slug] とオプショナル [[...slug]]
generateStaticParams:静的生成のタイミングとオンデマンド戦略
型安全:コンパイル時の型と Zod による実行時検証

App Router と Pages Router の違い、事前生成とオンデマンドの使い分けも整理できたはずです。

次にできること

すぐ試す

  • プロジェクトに動的ルートを 1 つ作り、params 取得を確認
  • 深いパスが必要なら Catch-All を試す
  • TypeScript 型と Zod 検証を追加

さらに学ぶ

  • パラレルルート:同一ページで複数ルート(@folder
  • インターセプトルート:ページ遷移なしで別ルート表示((.)folder
  • ルートグループ(folder) で整理し URL は変えない
  • ミドルウェア:ルート単位の認可・リダイレクト

公式リソース

よくある問題の早見表

問題確認項目対処
動的ルートが 404フォルダ名、generateStaticParams角括弧を確認、静的生成設定を見直す
paramsanyTypeScript 設定strict 有効化、パラメータ型を定義
ビルドが遅いgenerateStaticParams の件数事前生成を減らす、オンデマンド生成
データが更新されないキャッシュ戦略revalidatedynamicParams を設定

最後に

Pages Router から App Router への移行は、多くの人が同じように苦労する道のりです。一度 App Router の考え方に慣れると、直感的で強力だと感じるはずです。

動的ルートはアプリの土台です。ここを固めれば、データ取得・キャッシュ・ミドルウェアもスムーズに学べます。

実践で困ったら:

  1. 公式ドキュメントの Troubleshooting を確認
  2. Next.js GitHub で関連 Issue を検索
  3. Next.js Discord で質問(英語だが反応は早い)

試行錯誤を恐れず、エディタを開いて動的ルートを組み立ててみてください。

Next.js 動的ルーティング設定の完全フロー

動的ルートの作成から型安全な実装までの手順

⏱️ 目安時間: 2 時間

  1. 1

    ステップ1: 動的ルート用フォルダを作成する

    要件に応じてルートタイプを選ぶ:
    • 単一パラメータ:app/posts/[id]/page.tsx
    • 複数パラメータ:app/posts/[category]/[id]/page.tsx
    • Catch-all:app/posts/[...slug]/page.tsx
    • オプショナル catch-all:app/posts/[[...slug]]/page.tsx

    フォルダ命名ルール:
    • [id]:必須パラメータ
    • [...slug]:すべてのパスセグメントをキャプチャ
    • [[...slug]]:オプショナルにすべてのパスセグメントをキャプチャ
  2. 2

    ステップ2: ルートパラメータを取得する

    page.tsx でパラメータを取得する:
    • App Router では params オブジェクトを使う
    • params は Promise のため await が必要
    • 分割代入で個別のパラメータを取り出す

    例:
    export default async function Page({ params }) {
    const { id } = await params
    return <div>Post {id}</div>
    }

    注意:params は必ず await すること。さもないとエラーになる
  3. 3

    ステップ3: 型安全を設定する

    TypeScript で型を定義する:
    • params 用のインターフェースを定義
    • Promise<{ params }> 型を使う
    • generateStaticParams の戻り値型も定義

    例:
    interface PageProps {
    params: Promise<{ id: string }>
    }

    export default async function Page({ params }: PageProps) {
    const { id } = await params
    // ...
    }
  4. 4

    ステップ4: 静的生成を実装する(任意)

    generateStaticParams を使う:
    • あり得るパラメータの組み合わせをすべて返す
    • async 関数でデータ取得も可能
    • 全ページを静的生成するときに使う

    例:
    export async function generateStaticParams() {
    const posts = await getPosts()
    return posts.map(post => ({ id: post.id }))
    }

    注意:静的生成専用。純粋な動的ルートには不要
  5. 5

    ステップ5: オプショナルパラメータを扱う

    オプショナル catch-all ルート:
    • [[...slug]] 構文を使う
    • params.slug は undefined になり得る
    • パラメータの有無をチェックする

    例:
    export default async function Page({ params }) {
    const { slug } = await params
    if (!slug) {
    return <div>All posts</div>
    }
    return <div>Category: {slug.join('/')}</div>
    }
  6. 6

    ステップ6: テストと検証

    テストのポイント:
    • すべてのルートが正常に動くか
    • パラメータ取得が正しいか
    • 型ヒントが期待どおりか
    • 静的生成が成功するか

    チェックリスト:
    • すべての動的ルートにアクセスできる
    • パラメータの型定義が正しい
    • generateStaticParams が正しいデータを返す
    • 404 処理が済んでいる

FAQ

動的ルートのパラメータはどう取得しますか?
App Router では params オブジェクトで取得します。

ポイント:
• params は Promise のため必ず await
• 分割代入で個別のパラメータを取り出す
• 型定義が必要

例:
export default async function Page({ params }) {
const { id } = await params
return <div>{id}</div>
}
動的ルートが 404 になるのはなぜですか?
考えられる原因:
• フォルダ名の誤り({id} ではなく [id])
• パス不一致(URL とフォルダ構造を確認)
• generateStaticParams の返却データが不完全
• page.tsx がない

対処:
• フォルダ命名を確認
• URL パスとフォルダ構造が一致しているか確認
• generateStaticParams の戻り値を確認
catch-all とオプショナル catch-all の違いは?
catch-all ルート [...slug]:
• 少なくとも 1 つのパスセグメントが必要
• /posts/[...slug] は /posts/a にマッチするが /posts にはマッチしない

オプショナル catch-all [[...slug]]:
• 0 個以上のパスセグメントにマッチ
• /posts/[[...slug]] は /posts と /posts/a/b の両方にマッチ

使い分け:
• catch-all:最低 1 つのパラメータが必要なとき
• オプショナル catch-all:パラメータが任意のとき
型安全な動的ルートはどう実装しますか?
手順:
1) params 用の型インターフェースを定義
2) Promise<{ params }> 型を使う
3) generateStaticParams の戻り値型を定義

例:
interface PageProps {
params: Promise<{ id: string }>
}

export default async function Page({ params }: PageProps) {
const { id } = await params
// ...
}
generateStaticParams はいつ使いますか?
あり得るページをすべて静的生成するときに使います。

向いている場面:
• 取りうるパラメータ値がわかっている
• 全ページを静的生成したい
• パフォーマンスと SEO を上げたい

向かない場面:
• パラメータが動的に変わる
• 列挙しきれないほど多い
• リアルタイムデータが必要

注意:静的生成専用。純粋な動的ルートには不要
Pages Router から動的ルートを移行するには?
主な変化:
• getStaticPaths → generateStaticParams
• context.params → params(await が必要)
• 戻り値は { paths, fallback } から配列へ

移行手順:
1) getStaticPaths を generateStaticParams に置き換え
2) パラメータ取得を await params に変更
3) 型定義を更新
4) すべてのルートをテスト
複数パラメータの動的ルートはどう扱いますか?
多層フォルダを作成:
app/posts/[category]/[id]/page.tsx

パラメータ取得:
export default async function Page({ params }) {
const { category, id } = await params
return <div>{category} - {id}</div>
}

generateStaticParams はすべての組み合わせを返す:
export async function generateStaticParams() {
return [
{ category: 'tech', id: '1' },
{ category: 'tech', id: '2' },
// ...
]
}

5分で読めます · 公開日: 2025年12月25日 · 更新日: 2026年6月8日

関連記事

コメント

GitHubアカウントでログインしてコメントできます