Next.js の App Router で app/blog/[slug]/page.tsx のような動的ルートを作ると、URL に含まれる slug の値をコードから取り出したくなります。サーバーコンポーネントなら props の params で受け取れますが、"use client" を付けたクライアントコンポーネントでは props が届かない場所も多く、そこで役立つのが next/navigation の useParams フックです。この記事では、useParams の基本的な使い方、[id] と [...slug] で返り値の型がどう変わるか、サーバー側の params props との使い分け、ジェネリクスによる型付け、そしてつまずきやすいポイントまでを動くコードとあわせて解説します。
目次
useParams とは|いつ使うのか
useParams は、現在の URL の動的ルートパラメータをオブジェクトで返すクライアントコンポーネント用のフックです。next/navigation からインポートして使います。たとえば app/blog/[slug]/page.tsx というルートで /blog/hello にアクセスしているとき、useParams() は { slug: 'hello' } を返します。React の他のフックと同じく、レンダリングのたびに現在の URL に対応した値を返してくれます。
注意したいのは、useParams はフックなのでクライアントコンポーネントでしか呼べないという点です。ファイルの先頭に "use client" を書いたコンポーネントの中で使います。「ボタンの中の動的パラメータを使いたい」「ネストした共通コンポーネントで現在の id を参照したい」といった、props で params を受け取れない場所で URL のパラメータが欲しいときに向いています。逆に、ページのトップにあるサーバーコンポーネントでパラメータが欲しいだけなら、後述する props の params を使うのが基本です。
useParams の基本的な使い方
まずは [slug] の動的ルートで useParams を使う最小の例です。ディレクトリ構成は、角括弧付きのフォルダ名で動的セグメントを表します。app/blog/[slug]/page.tsx と置くと、/blog/なにか がこのページにマッチし、その「なにか」の部分が slug というパラメータになります。
app/
└─ blog/
└─ [slug]/
└─ page.tsx ← /blog/hello などにマッチする
このページをクライアントコンポーネントとして書き、useParams でパラメータを取り出します。ファイル先頭の "use client" が必須である点に注意してください。これがないとサーバーコンポーネント扱いになり、フックが使えずエラーになります。
'use client';
import { useParams } from 'next/navigation';
export default function BlogPage() {
// 現在の URL の動的パラメータをオブジェクトで受け取る
const params = useParams();
// /blog/hello なら params.slug は 'hello'
return <h1>現在の slug: {params.slug}</h1>;
}
/blog/hello にアクセスすると params.slug は 'hello' になり、画面には「現在の slug: hello」と表示されます。useParams() は引数を取らず、現在の URL に対応するパラメータのオブジェクトをそのまま返すだけのシンプルなフックです。ページ本体だけでなく、この page.tsx から呼び出されるネストしたクライアントコンポーネントの中でも、同じように useParams() で現在のパラメータを参照できます。
返り値の形|[id] は文字列、[…slug] は配列
useParams が返すオブジェクトの各値は、ルートの動的セグメントの種類によって文字列か配列かが変わります。通常の [id] のような単一セグメントなら文字列(string)、[...slug] のようなキャッチオール(catch-all)セグメントなら文字列の配列(string[])になります。次の表で整理します。
| ルート(フォルダ名) | アクセスする URL | useParams() の返り値 |
|---|---|---|
app/products/[id] | /products/42 | { id: '42' }(string) |
app/blog/[slug] | /blog/hello | { slug: 'hello' }(string) |
app/shop/[...slug] | /shop/a/b/c | { slug: ['a', 'b', 'c'] }(string[]) |
app/[category]/[id] | /tech/42 | { category: 'tech', id: '42' } |
キャッチオールセグメントは、/shop/a/b/c のように何階層でもマッチし、各セグメントが配列の要素になります。そのため配列として扱う必要があり、たとえば params.slug.join('/') のようにつなげて使います。同じ名前でも単一セグメントなら文字列、キャッチオールなら配列と型が違うため、ルートの形に合わせて扱い方を変える点に注意してください。なお、動的セグメントを持たないルートで useParams() を呼ぶと、空のオブジェクト {} が返ります。
'use client';
import { useParams } from 'next/navigation';
export default function ShopPage() {
const params = useParams();
// /shop/a/b/c なら params.slug は ['a', 'b', 'c']
// 配列なので join でパス文字列に戻せる
const path = Array.isArray(params.slug)
? params.slug.join('/')
: params.slug;
return <p>現在のパス: {path}</p>;
}
サーバーの params props との使い分け
ここが最も混同しやすいところです。App Router では、パラメータの取り出し方がサーバーコンポーネントとクライアントコンポーネントで異なります。ページやレイアウトのようなサーバーコンポーネントには、Next.js が params を props として渡してくれます。つまりサーバー側では useParams を使わず、props の params を受け取るのが基本です。
// "use client" を付けない=サーバーコンポーネント
// params は props として渡ってくる(Next.js 15 では Promise)
export default async function BlogPage({
params,
}: {
params: Promise<{ slug: string }>;
}) {
const { slug } = await params;
return <h1>現在の slug: {slug}</h1>;
}
サーバーコンポーネントでは、このように props の params を使います(Next.js 15 以降では params が Promise になっているため await して取り出します)。useParams はフックなのでサーバーコンポーネントでは呼べません。ではいつ useParams を使うかというと、props で params を受け取れないクライアントコンポーネントのときです。次の表で使い分けをまとめます。
| 場所 | パラメータの取り出し方 | 備考 |
|---|---|---|
| ページ・レイアウト(サーバー) | props の params | 基本はこちら。Next.js 15 では await が必要 |
| ネストしたクライアントコンポーネント | useParams() | props で params が届かない場所で使う |
| クライアントな page.tsx | useParams() | "use client" を付けたページ本体でも可 |
具体的には、ページ本体はサーバーコンポーネントのままにして、その中に置いた「共通のパンくずリスト」や「タブ切り替え」などのクライアントコンポーネントで現在の id や slug を参照したい、という場面です。そうしたコンポーネントは階層が深く、上から props で params をバケツリレーするのは面倒です。useParams を使えば、どこに置かれていても現在の URL のパラメータを直接取り出せます。props で受け取れるならその props を、受け取れないクライアント側でだけ useParams を、と考えると迷いません。
ジェネリクスで返り値に型を付ける
そのままの useParams() の返り値は、string | string[] | undefined を値に持つ緩い型になっています。どのパラメータが存在するか、文字列か配列かは TypeScript には分からないためです。ルートの形が分かっている場合は、ジェネリクスで返り値の型を明示できます。useParams<{ slug: string }>() のように型引数を渡すと、返り値の型がその形になり、params.slug を string として安全に扱えます。
'use client';
import { useParams } from 'next/navigation';
export default function BlogTitle() {
// ジェネリクスで返り値の形を指定する
const params = useParams<{ slug: string }>();
// params.slug が string として補完・型チェックされる
return <h1>{params.slug.toUpperCase()}</h1>;
}
キャッチオールセグメントを含む場合は、その値を配列型で指定します。たとえば useParams<{ slug: string[] }>() とすれば params.slug が string[] になり、join などの配列メソッドを型エラーなく呼べます。ジェネリクスに渡す型は、あくまで開発者がルートの形を宣言するものです。実際の URL と食い違う型を書いても実行時に変換されるわけではないので、ルートのフォルダ構成に合った型を指定してください。
useParams が undefined になる・使えないとき
useParams がうまく動かないときの原因は、ほとんどが「呼べる場所で呼べていない」ことに集約されます。代表的なつまずきを順に見ていきます。
“use client” を書き忘れている
App Router では、ファイルに "use client" を書かないコンポーネントはサーバーコンポーネントとして扱われます。useParams はクライアント用のフックなので、サーバーコンポーネントで呼ぶと「フックはクライアントコンポーネントでのみ使える」という趣旨のエラーになります。ファイルの先頭行に 'use client' があるかをまず確認してください。フックを使うコンポーネントのファイルに書く必要があり、親に書いてあるだけでは足りない場合があります。
next/router から import している
App Router で使う useParams は next/navigation にあります。Pages Router 時代の next/router(useRouter の query でパラメータを取る方式)とは別物です。import 元を間違えると正しく動かないので、import { useParams } from 'next/navigation'; になっているかを確認しましょう。
そのルートに動的セグメントが無い
参照したいパラメータ名のフォルダ([slug] など)が実際のルートに存在しないと、params.slug は undefined になります。角括弧付きのフォルダ名になっているか、参照している名前とフォルダ名が一致しているかを確認してください。動的セグメントを一切持たないルートで呼んだ場合、useParams() 自体は空オブジェクト {} を返し、そのプロパティにアクセスすると undefined になります。
useRouter・usePathname・useSearchParams との違い
next/navigation には URL に関するフックが複数あり、役割が違います。useParams は動的ルートのパラメータを取るものですが、URL の別の情報が欲しいときは別のフックを使います。混同しないよう整理しておきます。
| フック | 取得できるもの | 例 |
|---|---|---|
useParams() | 動的ルートのパラメータ | /blog/hello → { slug: 'hello' } |
usePathname() | 現在のパス文字列 | /blog/hello |
useSearchParams() | クエリ文字列(?key=value) | ?page=2 の page |
useRouter() | 画面遷移などの操作用オブジェクト | router.push('/') |
たとえば /blog/hello?page=2 というURLで、useParams() は { slug: 'hello' } を、useSearchParams() は page=2 を返します。パスの一部(動的セグメント)が欲しいなら useParams、? 以降のクエリが欲しいなら useSearchParams、というように取りたい情報で使い分けます。いずれも next/navigation のクライアント用フックです。
まとめ
useParams は、App Router のクライアントコンポーネントで現在の URL の動的ルートパラメータをオブジェクトで取得するフックです。next/navigation からインポートし、"use client" を付けたコンポーネントで const params = useParams(); のように呼びます。返り値は、単一セグメント [id] なら文字列、キャッチオール [...slug] なら文字列の配列になります。パラメータの取り出しは、サーバーコンポーネントでは props の params を使うのが基本で、useParams は props で params を受け取れないネストしたクライアントコンポーネントで使う、という使い分けがポイントです。型は useParams<{ slug: string }>() のようにジェネリクスで指定できます。うまく動かないときは、"use client" の有無、next/navigation からのインポート、ルートに動的セグメントがあるかを順に確認しましょう。