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-world→slug = "hello-world"/blog/nextjs-guide→slug = "nextjs-guide"/blog/123→slug = "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" になります。
初心者が陥りやすいミス:
- ❌ ファイル名を
[slug].tsxにする(App Router ではフォルダが必要) - ❌
props.slugを直接参照する(params経由で取得) - ❌ フォルダ名の角括弧を忘れる(角括弧がないと静的ルート)
Pages Router と App Router の比較
Pages Router を使ったことがあると違和感があるかもしれません。以前は pages/blog/[slug].tsx でしたが、App Router ではかなり変わっています。
| 項目 | Pages Router | App Router |
|---|---|---|
| ファイル位置 | pages/blog/[slug].tsx | app/blog/[slug]/page.tsx |
| パラメータ取得 | router.query.slug または getStaticProps | params.slug |
| 型定義 | 手動定義が多い | props 型から推論しやすい |
| 静的生成 | getStaticPaths | generateStaticParams |
移行時にいちばん慣れないのがパラメータ取得です。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>
)
}
押さえるポイント:
- コンポーネントを
asyncにしている(Server Components は非同期可) - 先にデータ取得し、結果に応じて描画を分岐
- 商品がない場合を処理(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-started→slug = ["getting-started"]/docs/api/authentication→slug = ["api", "authentication"]/docs/guides/deployment/vercel→slug = ["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>
)
}
このコードのポイント:
slugArray.join('/')でパス配列を文字列化sliceでパス接頭辞を切り出し、パンくずを実装params: { slug: string[] }で TypeScript が型チェック
オプショナル Catch-All:[[...slug]]
/docs も /docs/* もマッチさせたいときは、オプショナル Catch-All を使います。通常の Catch-All は /docs(パラメータなし)にはマッチしません。
app/
├── docs/
│ └── [[...slug]]/
│ └── page.tsx ← 二重の角括弧に注意
マッチ例:
/docs→slug = undefined/docs/getting-started→slug = ["getting-started"]/docs/api/auth→slug = ["api", "auth"]
コードでは slug が undefined になり得ることを処理します。
// 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/123 | string |
| Catch-All | [...slug] | /docs/a/b/c(/docs は含まない) | string[] |
| オプショナル Catch-All | [[...slug]] | /docs と /docs/a/b/c | string[] | 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>
)
}
流れ:
- ビルド時に
generateStaticParamsが全 slug を返す - Next.js が各 slug 用の静的 HTML を生成
- アクセス時は静的ファイルを返すため高速
ビルド後の例:
.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 = true、revalidate などがあります。
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.slug は string 型ですが、実際には数値 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>
)
}
この実装の強み:
Localeで"zh" | "en" | "ja"に限定generateStaticParamsの戻り値がPageParams[]で構造を保証- Zod でランタイム検証
- 型定義から実行時まで一貫
よくある型の問題
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 になる
確認項目:
tsconfig.jsonで strict が有効か- Next.js の型を正しく import しているか
- ファイル名が
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 | 角括弧を確認、静的生成設定を見直す |
params が any | TypeScript 設定 | strict 有効化、パラメータ型を定義 |
| ビルドが遅い | generateStaticParams の件数 | 事前生成を減らす、オンデマンド生成 |
| データが更新されない | キャッシュ戦略 | revalidate や dynamicParams を設定 |
最後に
Pages Router から App Router への移行は、多くの人が同じように苦労する道のりです。一度 App Router の考え方に慣れると、直感的で強力だと感じるはずです。
動的ルートはアプリの土台です。ここを固めれば、データ取得・キャッシュ・ミドルウェアもスムーズに学べます。
実践で困ったら:
- 公式ドキュメントの Troubleshooting を確認
- Next.js GitHub で関連 Issue を検索
- Next.js Discord で質問(英語だが反応は早い)
試行錯誤を恐れず、エディタを開いて動的ルートを組み立ててみてください。
Next.js 動的ルーティング設定の完全フロー
動的ルートの作成から型安全な実装までの手順
⏱️ 目安時間: 2 時間
- 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: ルートパラメータを取得する
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: 型安全を設定する
TypeScript で型を定義する:
• params 用のインターフェースを定義
• Promise<{ params }> 型を使う
• generateStaticParams の戻り値型も定義
例:
interface PageProps {
params: Promise<{ id: string }>
}
export default async function Page({ params }: PageProps) {
const { id } = await params
// ...
} - 4
ステップ4: 静的生成を実装する(任意)
generateStaticParams を使う:
• あり得るパラメータの組み合わせをすべて返す
• async 関数でデータ取得も可能
• 全ページを静的生成するときに使う
例:
export async function generateStaticParams() {
const posts = await getPosts()
return posts.map(post => ({ id: post.id }))
}
注意:静的生成専用。純粋な動的ルートには不要 - 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: テストと検証
テストのポイント:
• すべてのルートが正常に動くか
• パラメータ取得が正しいか
• 型ヒントが期待どおりか
• 静的生成が成功するか
チェックリスト:
• すべての動的ルートにアクセスできる
• パラメータの型定義が正しい
• generateStaticParams が正しいデータを返す
• 404 処理が済んでいる
FAQ
動的ルートのパラメータはどう取得しますか?
ポイント:
• 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 の違いは?
• 少なくとも 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日
Next.js 完全ガイド
検索からこのページに来た場合は、前後の記事もあわせて読むと同じテーマの理解がかなり早く深まります。
前の記事
Next.js App Router 実践:ルートグループとネストレイアウトで大規模プロジェクトのディレクトリ混乱を解決する
ルートグループ、ネストレイアウト、パラレルルート、インターセプトルートの 4 機能で Next.js 大規模プロジェクトのディレクトリ混乱・ルート競合・チーム協業問題を解決し、すぐ使える完全なディレクトリ構造を提供します。
第 9 / 47 記事
次の記事
Next.js App Router よくある落とし穴と解決策:遠回りを減らす 8 つの実践知見
データ取得からエラー処理まで、Next.js App Router 開発で最もハマりやすい 14 の落とし穴と解決策を総まとめ。Server Components と Client Components の混同、キャッシュ、移行時の問題など、80% のよくあるミスを避ける実践知見を紹介します。
第 11 / 47 記事
関連記事
Next.js App Router 入門ガイド:コア概念と基本操作を解説
Next.js App Router 入門ガイド:コア概念と基本操作を解説
Next.js 15 実践:週末で本番級ブログシステムを構築した方法
Next.js 15 実践:週末で本番級ブログシステムを構築した方法
Next.js Middleware 実践ガイド:パスマッチ、Edge Runtime 制限とよくある落とし穴
コメント
GitHubアカウントでログインしてコメントできます