概要
Next.js 15 の forbidden() 関数を使い、認証済みユーザーが自分に権限のないリソースにアクセスした場合に HTTP 403 相当の専用ページを表示する。unauthorized() との使い分け(401 vs 403 のセマンティクス)と、forbidden.tsx によるカスタム UI の実装を示す。
インストール
# 追加インストールは不要
実装
forbidden() を呼び出す Server Component
// app/admin/page.tsx
import { forbidden } from "next/navigation";
import { getCurrentUser } from "@/lib/auth";
export default async function AdminPage() {
const user = await getCurrentUser();
// 未ログインは unauthorized(401)
if (!user) {
// redirect to login or call unauthorized()
return null;
}
// ログイン済みだが admin ロールがない場合は forbidden(403)
if (user.role !== "admin") {
forbidden();
}
return (
<div>
<h1>管理者ページ</h1>
{/* ...管理コンテンツ */}
</div>
);
}
forbidden.tsx でカスタム UI を定義する
forbidden.tsx を app/ 直下またはセグメント内に置くと、forbidden() が呼ばれたときにそのコンポーネントが表示される。
// app/forbidden.tsx
export default function ForbiddenPage() {
return (
<div className="flex min-h-[60vh] flex-col items-center justify-center gap-4 text-center">
<p className="text-6xl font-bold text-gray-300">403</p>
<h1 className="text-xl font-semibold text-gray-800">
このページへのアクセス権限がありません
</h1>
<p className="text-sm text-gray-500">
アクセスするには管理者権限が必要です。
</p>
<a
href="/"
className="mt-2 inline-block rounded bg-blue-600 px-4 py-2 text-sm text-white hover:bg-blue-700"
>
トップページへ戻る
</a>
</div>
);
}
リソース単位での権限チェック
// app/projects/[id]/page.tsx
import { forbidden } from "next/navigation";
import { getCurrentUser, getProject } from "@/lib/auth";
type Props = {
params: Promise<{ id: string }>;
};
export default async function ProjectPage({ params }: Props) {
const { id } = await params;
const [user, project] = await Promise.all([
getCurrentUser(),
getProject(id),
]);
if (!user) {
// 未ログインは認証ページへ
return null;
}
// プロジェクトメンバーでなければ forbidden
if (!project.memberIds.includes(user.id)) {
forbidden();
}
return (
<div>
<h1>{project.name}</h1>
</div>
);
}
unauthorized との使い分け
| 状況 | 関数 | HTTP ステータス |
|---|---|---|
| 未ログイン・セッション切れ | unauthorized() | 401 |
| ログイン済みだが権限不足 | forbidden() | 403 |
// unauthorized.tsx(参考)
// app/unauthorized.tsx
export default function UnauthorizedPage() {
return (
<div className="flex min-h-[60vh] flex-col items-center justify-center gap-4 text-center">
<p className="text-6xl font-bold text-gray-300">401</p>
<h1 className="text-xl font-semibold text-gray-800">
ログインが必要です
</h1>
<a
href="/login"
className="mt-2 inline-block rounded bg-blue-600 px-4 py-2 text-sm text-white hover:bg-blue-700"
>
ログインページへ
</a>
</div>
);
}
ポイント
forbidden()は Next.js 15 で追加された関数で、呼び出すと即座にレンダリングを中断しforbidden.tsxの UI を表示する。throw不要でコードが簡潔になるforbidden.tsxはnot-found.tsxと同じ仕組みで、ファイルを置いたセグメントとその配下に適用される。app/forbidden.tsxはアプリ全体のデフォルト- 「未ログイン(401)」と「権限不足(403)」は異なる HTTP セマンティクスを持つ。未ログインは
unauthorized()+unauthorized.tsx、権限不足はforbidden()+forbidden.tsxと使い分けることで、ユーザーに適切な案内を出せる - 権限チェックはリソース取得と並行して行える(
Promise.all)。取得結果に基づく判定なので、存在しないリソースにはnotFound()を使い、存在するが見せられないリソースにはforbidden()を使う forbidden()は Server Component でのみ動作する。Client Component から呼ぶ場合は API Route や Server Action 経由でステータスを返してリダイレクトする