比較表
| Middleware | Layout 保護 | Server Component 個別確認 | |
|---|---|---|---|
| 実行タイミング | ルート到達前(Edge) | レイアウト描画時(Server) | ページ描画時(Server) |
| 保護の単位 | パターンマッチ(複数ルート一括) | レイアウト配下全体 | ページ単位 |
| 柔軟な条件分岐 | 限定的 | 中程度 | 高い |
| リダイレクト方法 | NextResponse.redirect() | redirect()(next/navigation) | redirect()(next/navigation) |
| Node.js ライブラリ使用 | 不可(Edge Runtime) | 可 | 可 |
| 重複リスク | 低い | 低い | ページ数が増えると高くなる |
各方式が向く場面
Middleware
// middleware.ts
import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";
export function middleware(request: NextRequest) {
const session = request.cookies.get("session_id");
if (!session) {
return NextResponse.redirect(new URL("/login", request.url));
}
return NextResponse.next();
}
export const config = {
matcher: ["/dashboard/:path*", "/admin/:path*"],
};
向く場面:
- アプリ全体、または
/dashboard配下など複数ルートをまとめてガードしたいとき - セッション Cookie の有無だけで判定できる単純な認証チェック
注意点: Middleware は Edge Runtime で動作するため、Prisma などの Node.js 専用ライブラリは使えません。Cookie の読み取りや JWT の検証など、Edge で動く軽い処理に限定します。重いロジックは Server Component に任せます。
Layout 保護
// app/(dashboard)/layout.tsx
import { cookies } from "next/headers";
import { redirect } from "next/navigation";
export default async function DashboardLayout({ children }) {
const cookieStore = await cookies();
if (!cookieStore.get("session_id")) {
redirect("/login");
}
return <div>{children}</div>;
}
向く場面:
- ルートグループ(
(dashboard)など)配下のページをまとめて保護したいとき - Prisma など Node.js ライブラリでセッション検証をしたいとき
- レイアウト共通のデータ取得(ユーザー情報など)と認証チェックをまとめたいとき
注意点: レイアウトは配下のすべてのページで実行されます。レイアウト内で例外が発生すると配下全体に影響するため、エラーハンドリングを適切に設けます。
Server Component 個別確認
// app/dashboard/settings/page.tsx
import { cookies } from "next/headers";
import { redirect } from "next/navigation";
export default async function SettingsPage() {
const cookieStore = await cookies();
if (!cookieStore.get("session_id")) {
redirect("/login");
}
// ページ固有の処理
}
向く場面:
- ページごとに異なる認証条件(権限レベルの違いなど)を持たせたいとき
- 認証状態によって表示内容を細かく切り替えたいとき
- 認証チェックとデータ取得を同じコンポーネントで管理したいとき
注意点: ページが増えると認証チェックのコードが重複します。共通のヘルパー関数(例: requireAuth())に切り出して再利用するパターンが有効です。
実行タイミングの違い
リクエスト
│
▼
【Middleware】← ルートに到達する前(Edge)
│ 未認証 → /login へリダイレクト
│
▼
【Layout 保護】← レイアウト描画時(Server)
│ 未認証 → /login へリダイレクト
│
▼
【Server Component】← ページ描画時(Server)
未認証 → /login へリダイレクト
早い段階で弾くほど後続処理のコストが減ります。ただし Edge での実行制約(Middleware)と Node.js での実行柔軟性(Layout / Server Component)のトレードオフがあります。
ルート単位 / コンポーネント単位の違い
- Middleware:
matcherで URL パターンを指定して複数ルートを一括保護。コンポーネントツリーの外で動くため、React の Context や Server Component の機能は使えない - Layout 保護: App Router のレイアウト階層に従って保護範囲が決まる。
(dashboard)などのルートグループと組み合わせるとセクション単位の保護が自然に書ける - Server Component 個別確認: ページ単位で条件を自由に書ける。
if (user.role !== 'admin') redirect('/403')のような権限チェックも自然
よくある選び方
ルート全体を一括でガードしたいか?
├── Yes → Middleware(Cookie の有無だけで判定できるなら最適)
│ ※ Edge で動かない処理が必要なら Layout 保護に
└── No
├── セクション(/(dashboard) など)単位でまとめて保護したいか?
│ └── Yes → Layout 保護
└── ページごとに条件・表示を柔軟に制御したいか?
└── Yes → Server Component 個別確認
※ 重複を防ぐため requireAuth() 関数に共通化する
組み合わせパターン
3方式は排他的ではありません。典型的な組み合わせ:
- Middleware + Server Component: Middleware でルートを一括ガード(Cookie の存在チェック)し、Server Component でセッションの有効性確認とユーザー情報取得を行う
- Layout + Server Component: Layout でセクション全体を保護しつつ、各ページで権限レベルに応じた表示切り替えを行う
二重に保護する場合は「どちらが何の責務を持つか」を明確にしておくと、後から修正しやすくなります。
関連サンプルの見どころ
- nextjs-middleware-auth: Middleware での Cookie チェックとリダイレクトパターン
- nextjs-protected-layout: Layout 保護でルートグループをまとめてガードするパターン
- nextjs-auth-redirect: Server Component でセッション確認して条件分岐リダイレクトするパターン
未認証・権限不足・フォーム失敗の失敗分岐の判断 → 認証・権限チェックの失敗分岐