Skip to content

Next.jsのルーティング詳細

Next.jsは、ファイルシステムに基づいたルーティングを採用しています。これにより、プロジェクト内のディレクトリやファイルがそのままURLパスになります。Next.js 13以降、新しいApp Routerが導入され、既存のPages Routerと異なるアプローチでルーティングを管理します。

特徴Pages RouterApp Router
ディレクトリpages/app/
動的セグメント[name].tsx[name]/page.tsx
パス取得useRouter().queryparamsプロパティ
サーバーコンポーネント非対応対応
レイアウト共有_app.tsxlayout.tsx
データフェッチングgetServerSideProps, getStaticPropsasyncコンポーネント
推奨既存プロジェクト新規プロジェクト

App Routerは、app/ディレクトリを使用してルーティングを管理します。このアプローチでは、レイアウトの共有、ストリーミング、サーバーコンポーネントの利用が容易になります。

ファイルベースのルーティング (app/)

Section titled “ファイルベースのルーティング (app/)”

app/ディレクトリ内のサブディレクトリがURLパスに対応します。各セグメントのpage.tsxファイルが、そのパスのUIを定義します。

ファイルシステムとURLの対応関係

Section titled “ファイルシステムとURLの対応関係”

以下の図は、ファイルシステムの構造がどのようにURLパスにマッピングされるかを示しています。

ファイルシステム構造 URLパス
───────────────────────────── ─────────────────────────
app/
├── page.tsx → /
├── about/
│ └── page.tsx → /about
├── blog/
│ ├── page.tsx → /blog
│ ├── [slug]/
│ │ └── page.tsx → /blog/[slug]
│ └── [slug]/
│ └── [commentId]/
│ └── page.tsx → /blog/[slug]/[commentId]
└── shop/
├── [category]/
│ └── [productId]/
│ └── page.tsx → /shop/[category]/[productId]
└── [[...slug]]/
└── page.tsx → /shop/[[...slug]]

重要なポイント:

  • ディレクトリ名 = URLセグメント: ディレクトリ名がそのままURLパスのセグメントになります
  • page.tsx = ページコンポーネント: 各ルートにはpage.tsxファイルが必要です
  • [param] = 動的セグメント: 角括弧で囲まれた名前が動的パラメータになります
  • [[...slug]] = オプショナルキャッチオール: 二重角括弧はオプショナルなキャッチオールルートです
/app
├── layout.tsx // 共有レイアウト(例: ナビゲーションバー)
├── page.tsx // ルートページ (/)
├── about/
│ └── page.tsx // /aboutページ
└── blog/
├── layout.tsx // /blog配下の共通レイアウト
├── page.tsx // /blogページ
└── [slug]/
└── page.tsx // /blog/[slug]ページ
app/about/page.tsx
export default function AboutPage() {
return (
<div>
<h1>About Us</h1>
<p>私たちについて</p>
</div>
);
}

特徴:

  • 自動ルーティング: app/about/page.tsxが自動的に/aboutルートになる
  • デフォルトエクスポート: page.tsxはデフォルトエクスポートが必要
  • サーバーコンポーネント: デフォルトでサーバーコンポーネントとして動作

動的なパスセグメントは、角括弧[]で囲んで指定します。例えば、[slug]というディレクトリは、/blog/first-post/blog/second-postのようなURLに対応します。

Next.js 15の変更点: Next.js 15以降では、paramsはPromiseとして扱われるため、awaitが必要です。Next.js 14以前でも動作するように、awaitを使用することを推奨します。

app/blog/[slug]/page.tsx
type BlogPostPageProps = {
params: Promise<{
slug: string;
}>;
};
export default async function BlogPostPage({ params }: BlogPostPageProps) {
// Next.js 15以降: paramsをawaitする必要がある
const { slug } = await params;
// サーバーコンポーネントなので、直接データフェッチングが可能
const post = await fetch(`https://api.example.com/posts/${slug}`)
.then(res => res.json());
return (
<div>
<h1>{post.title}</h1>
<p>{post.content}</p>
</div>
);
}

特徴:

  • paramsプロパティ: 動的なセグメントの値がparamsプロパティとして渡される(Next.js 15以降はPromise)
  • 型安全性: TypeScriptで型定義が可能
  • サーバーコンポーネント: async関数として定義可能
  • Next.js 15対応: paramsawaitで解決する必要がある
app/shop/[category]/[product]/page.tsx
type ProductPageProps = {
params: Promise<{
category: string;
product: string;
}>;
};
export default async function ProductPage({ params }: ProductPageProps) {
// Next.js 15以降: paramsをawaitする必要がある
const { category, product } = await params;
return (
<div>
<h1>カテゴリ: {category}</h1>
<h2>商品: {product}</h2>
</div>
);
}

URL例:

  • /shop/electronics/iphonecategory: 'electronics', product: 'iphone'
  • /shop/clothing/t-shirtcategory: 'clothing', product: 't-shirt'

💡 シニアの知恵: なぜ意味のある名前を使うのか?

ネストされた動的ルートでは、すべてのパラメータがparamsオブジェクトに含まれます。例えば、/users/[userId]/posts/[postId]という構造の場合、params{ userId: '...', postId: '...' }となります。

すべてを[id]にしてしまうと、どのidがどのセグメントを指しているのかわからなくなり、バグの原因になります。「その変数が指す固有の名前」userIdpostIdなど)を常に使いましょう。

app/users/[userId]/posts/[postId]/page.tsx
// ✅ 良い例: 意味のある名前を使用
type PostPageProps = {
params: Promise<{
userId: string; // ユーザーIDであることが明確
postId: string; // 投稿IDであることが明確
}>;
};
export default async function PostPage({ params }: PostPageProps) {
const { userId, postId } = await params;
// userIdとpostIdの区別が明確
const post = await fetch(`/api/users/${userId}/posts/${postId}`);
// ...
}
// ❌ 悪い例: すべて[id]にしてしまう
// app/users/[id]/posts/[id]/page.tsx
type PostPageProps = {
params: Promise<{
id: string; // どちらのIDかわからない!
}>;
};
export default async function PostPage({ params }: PostPageProps) {
const { id } = await params;
// これはユーザーIDなのか、投稿IDなのかわからない
// バグの原因になる可能性が高い
}
app/docs/[...slug]/page.tsx
type DocsPageProps = {
params: Promise<{
slug: string[];
}>;
};
export default async function DocsPage({ params }: DocsPageProps) {
// Next.js 15以降: paramsをawaitする必要がある
const { slug } = await params;
// slugは配列として渡される
// /docs/getting-started/installation → ['getting-started', 'installation']
const path = slug.join('/');
return <div>ドキュメント: {path}</div>;
}

URL例:

  • /docs/getting-startedslug: ['getting-started']
  • /docs/getting-started/installationslug: ['getting-started', 'installation']

オプショナルキャッチオールルート

Section titled “オプショナルキャッチオールルート”
app/shop/[[...slug]]/page.tsx
type ShopPageProps = {
params: Promise<{
slug?: string[];
}>;
};
export default async function ShopPage({ params }: ShopPageProps) {
// Next.js 15以降: paramsをawaitする必要がある
const { slug } = await params;
if (!slug) {
return <div>ショップトップページ</div>;
}
return <div>ショップ: {slug.join('/')}</div>;
}

URL例:

  • /shopslug: undefined(オプショナル)
  • /shop/electronicsslug: ['electronics']
  • /shop/electronics/phonesslug: ['electronics', 'phones']

layout.tsxファイルを使用して、複数のページで共通のレイアウトを共有できます。

// app/layout.tsx(ルートレイアウト)
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html lang="ja">
<body>
<header>
<nav>
<a href="/">ホーム</a>
<a href="/about">About</a>
<a href="/blog">Blog</a>
</nav>
</header>
<main>{children}</main>
<footer>
<p>&copy; 2024 My App</p>
</footer>
</body>
</html>
);
}
// app/blog/layout.tsx(ブログ専用レイアウト)
export default function BlogLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<div className="blog-container">
<aside>
<h2>カテゴリ</h2>
<ul>
<li><a href="/blog/tech">技術</a></li>
<li><a href="/blog/life">生活</a></li>
</ul>
</aside>
<article>{children}</article>
</div>
);
}

特徴:

  • ネストされたレイアウト: レイアウトは階層的に適用される
  • レイアウトの永続化: ページ遷移時もレイアウトは再レンダリングされない
  • 状態の保持: レイアウト内の状態はページ遷移時も保持される

ルートグループを使用して、URLパスに影響を与えずにルートを整理できます。

/app
├── (marketing)/
│ ├── about/
│ │ └── page.tsx // /about
│ └── contact/
│ └── page.tsx // /contact
└── (shop)/
├── products/
│ └── page.tsx // /products
└── cart/
└── page.tsx // /cart

特徴:

  • URLに影響しない: 括弧で囲まれたディレクトリはURLパスに含まれない
  • レイアウトの分離: 異なるレイアウトを適用できる
  • 組織化: 関連するルートをグループ化できる

@folderという命名規則を使用して、同じURLに対して複数のページを同時に表示できます。

/app
└── dashboard/
├── @analytics/
│ └── page.tsx
├── @team/
│ └── page.tsx
└── page.tsx
app/dashboard/layout.tsx
export default function DashboardLayout({
analytics,
team,
children,
}: {
analytics: React.ReactNode;
team: React.ReactNode;
children: React.ReactNode;
}) {
return (
<div>
{children}
{analytics}
{team}
</div>
);
}

loading.tsxerror.tsxを使用して、ローディング状態とエラー状態を処理できます。

app/blog/[slug]/loading.tsx
export default function Loading() {
return <div>読み込み中...</div>;
}
app/blog/[slug]/error.tsx
'use client';
export default function Error({
error,
reset,
}: {
error: Error;
reset: () => void;
}) {
return (
<div>
<h2>エラーが発生しました</h2>
<button onClick={reset}>再試行</button>
</div>
);
}

クライアントコンポーネントでのルーティング

Section titled “クライアントコンポーネントでのルーティング”

App Routerでは、クライアントコンポーネントでナビゲーションを行う場合、next/navigationからuseRouterをインポートする必要があります。これは、Pages Routerのnext/routerとは別物です。

⚠️ 重要な注意点: next/router(Pages Router用)とnext/navigation(App Router用)は完全に別のパッケージです。混同しないように注意してください。

App Routerでのクライアントサイドルーティング

Section titled “App Routerでのクライアントサイドルーティング”
'use client';
// ✅ 正しい: App Routerでは next/navigation を使用
import { useRouter, usePathname, useSearchParams } from 'next/navigation';
export default function NavigationButton() {
const router = useRouter();
const pathname = usePathname();
const searchParams = useSearchParams();
const handleClick = () => {
// プログラムによるナビゲーション
router.push('/about');
// または
router.replace('/contact');
};
return (
<div>
<p>現在のパス: {pathname}</p>
<button onClick={handleClick}>Aboutページへ</button>
</div>
);
}

App RouterのuseRouternext/navigation)の特徴:

  • push(): 新しいページに遷移(ブラウザ履歴に追加)
  • replace(): 現在のページを置き換え(ブラウザ履歴に追加しない)
  • refresh(): 現在のルートを再フェッチ
  • back(): ブラウザの戻るボタンと同じ動作
  • forward(): ブラウザの進むボタンと同じ動作
  • queryプロパティは存在しない(これはPages Routerの機能)

Pages Routerでのクライアントサイドルーティング

Section titled “Pages Routerでのクライアントサイドルーティング”
// ✅ 正しい: Pages Routerでは next/router を使用
import { useRouter } from 'next/router';
export default function NavigationButton() {
const router = useRouter();
// Pages RouterのuseRouterはqueryプロパティを持つ
const { id, category } = router.query;
const handleClick = () => {
router.push('/about');
};
return (
<div>
<p>クエリパラメータ: {JSON.stringify(router.query)}</p>
<button onClick={handleClick}>Aboutページへ</button>
</div>
);
}

Pages RouterのuseRouternext/router)の特徴:

  • push(): 新しいページに遷移
  • replace(): 現在のページを置き換え
  • query: URLのクエリパラメータと動的ルートのパラメータにアクセス可能
  • pathname: 現在のパス名
  • asPath: クエリ文字列を含む実際のパス
機能App Router (next/navigation)Pages Router (next/router)
インポートimport { useRouter } from 'next/navigation'import { useRouter } from 'next/router'
push()✅ あり✅ あり
replace()✅ あり✅ あり
queryなし✅ あり
pathnameusePathname()フックを使用router.pathname
searchParamsuseSearchParams()フックを使用router.query

よくある間違い: インポートパスの混同

Section titled “よくある間違い: インポートパスの混同”
'use client';
// ❌ 間違い: App Routerで next/router を使用
import { useRouter } from 'next/router'; // エラーが発生する可能性
// ✅ 正しい: App Routerでは next/navigation を使用
import { useRouter } from 'next/navigation';
export default function MyComponent() {
const router = useRouter();
// ...
}

App Routerでクエリパラメータを取得する方法

Section titled “App Routerでクエリパラメータを取得する方法”

App Routerでは、queryプロパティが存在しないため、useSearchParamsフックを使用します。

'use client';
import { useSearchParams } from 'next/navigation';
export default function SearchPage() {
const searchParams = useSearchParams();
// クエリパラメータを取得
const query = searchParams.get('q');
const category = searchParams.get('category');
return (
<div>
<p>検索クエリ: {query}</p>
<p>カテゴリ: {category}</p>
</div>
);
}

URL例: /search?q=nextjs&category=framework

  • searchParams.get('q')'nextjs'
  • searchParams.get('category')'framework'

Pages Routerは、pages/ディレクトリを使用してルーティングを管理する従来のアプローチです。

ファイルベースのルーティング (pages/)

Section titled “ファイルベースのルーティング (pages/)”

pages/ディレクトリ内のファイル名がURLパスになります。

/pages
├── index.tsx // ルートページ (/)
├── about.tsx // /aboutページ
└── blog/
├── index.tsx // /blogページ
└── [id].tsx // /blog/[id]ページ
pages/about.tsx
export default function AboutPage() {
return (
<div>
<h1>About Us</h1>
<p>私たちについて</p>
</div>
);
}

Pages Routerでは、useRouterフックを使って動的なセグメントの値を取得します。

pages/blog/[id].tsx
import { useRouter } from 'next/router';
const BlogPostPage = () => {
const router = useRouter();
const { id } = router.query;
// クエリパラメータも取得可能
const { category } = router.query;
return (
<div>
<h1>ブログ投稿: {id}</h1>
{category && <p>カテゴリ: {category}</p>}
</div>
);
};
export default BlogPostPage;

特徴:

  • useRouterフック: クライアントサイドでのルーティング情報にアクセス
  • クエリパラメータ: URLのクエリパラメータもrouter.queryで取得可能
  • クライアントコンポーネント: デフォルトでクライアントコンポーネント

Pages Routerでは、getServerSidePropsgetStaticPropsを使用してデータをフェッチします。

pages/blog/[id].tsx
import { GetServerSideProps } from 'next';
type BlogPostPageProps = {
post: {
id: string;
title: string;
content: string;
};
};
export default function BlogPostPage({ post }: BlogPostPageProps) {
return (
<div>
<h1>{post.title}</h1>
<p>{post.content}</p>
</div>
);
}
export const getServerSideProps: GetServerSideProps = async (context) => {
const { id } = context.params!;
const post = await fetch(`https://api.example.com/posts/${id}`)
.then(res => res.json());
return {
props: {
post,
},
};
};

ルーティングのベストプラクティス

Section titled “ルーティングのベストプラクティス”

新しいプロジェクトを始める場合は、App Routerの利用が強く推奨されます。App Routerは、将来的なNext.jsの進化の方向性であり、サーバーコンポーネントやデータフェッチの新しい方法など、多くのパフォーマンス改善を提供します。

2. 動的ルートの命名規則(思想の深掘り)

Section titled “2. 動的ルートの命名規則(思想の深掘り)”

動的ルートの命名は、コードの可読性と保守性に大きく影響します。特に、ネストされたルートでは、意味のある名前を使用することが重要です。

Next.jsのApp Routerでは、paramsがネストされます。例えば、/users/[userId]/posts/[postId]という構造の場合、params{ userId: '...', postId: '...' }となります。

すべてを[id]にしてしまうと、どちらのIDかわからず、バグの原因になります。**「その変数が指す固有の名前」**を常に使いましょう。

// ✅ 良い例: 意味のある名前を使用
app/blog/[slug]/page.tsx
app/users/[userId]/page.tsx
app/products/[productId]/page.tsx
app/users/[userId]/posts/[postId]/page.tsx
// ❌ 悪い例: 意味のない名前
app/blog/[id]/page.tsx
app/users/[id]/page.tsx
app/users/[id]/posts/[id]/page.tsx // どちらのIDかわからない!
app/users/[userId]/posts/[postId]/page.tsx
// ✅ 良い例: 明確な命名
type PostPageProps = {
params: Promise<{
userId: string; // ユーザーIDであることが明確
postId: string; // 投稿IDであることが明確
}>;
};
export default async function PostPage({ params }: PostPageProps) {
const { userId, postId } = await params;
// userIdとpostIdの区別が明確で、コードが読みやすい
const post = await fetch(`/api/users/${userId}/posts/${postId}`);
return <div>{/* ... */}</div>;
}
// ❌ 悪い例: すべて[id]にしてしまう
// app/users/[id]/posts/[id]/page.tsx
type PostPageProps = {
params: Promise<{
id: string; // どちらのIDかわからない!
}>;
};
export default async function PostPage({ params }: PostPageProps) {
const { id } = await params;
// これはユーザーIDなのか、投稿IDなのかわからない
// バグの原因になる可能性が高い
const post = await fetch(`/api/users/${id}/posts/${id}`); // 間違っている可能性
return <div>{/* ... */}</div>;
}

命名規則のベストプラクティス

Section titled “命名規則のベストプラクティス”
  1. 固有の名前を使用: userIdpostIdproductIdなど、その変数が何を指しているか明確に
  2. 複数形を避ける: [users]ではなく[userId]を使用(単一のIDを表すため)
  3. 意味のある名前: [slug][username][category]など、その変数の意味が伝わる名前
  4. 一貫性を保つ: プロジェクト全体で命名規則を統一
// ✅ 良い例: 共通レイアウトを使用
app/layout.tsx // 全体のレイアウト
app/dashboard/layout.tsx // ダッシュボード専用レイアウト
// ❌ 悪い例: 各ページで個別にレイアウトを実装
app/dashboard/page.tsx // レイアウトを含む
app/dashboard/settings/page.tsx // 同じレイアウトを再度実装
app/blog/[slug]/error.tsx
'use client';
export default function Error({
error,
reset,
}: {
error: Error;
reset: () => void;
}) {
return (
<div>
<h2>エラーが発生しました</h2>
<p>{error.message}</p>
<button onClick={reset}>再試行</button>
</div>
);
}
app/blog/[slug]/loading.tsx
export default function Loading() {
return (
<div>
<div className="skeleton">読み込み中...</div>
</div>
);
}

Next.jsのルーティングは、ファイルシステムベースで直感的に理解できます。App RouterとPages Routerの違いを理解し、プロジェクトの要件に応じて適切なアプローチを選択することが重要です。

推奨事項:

  • 新規プロジェクト: App Routerを使用
  • 既存プロジェクト: Pages RouterからApp Routerへの移行を検討
  • 動的ルート: 意味のある名前を使用
  • レイアウト: 共通レイアウトを適切に使用
  • エラーハンドリング: error.tsxloading.tsxを活用

これらのベストプラクティスを守ることで、保守性が高く、パフォーマンスの良いNext.jsアプリケーションを構築できます。