レビュー済み·難易度: 初級·更新: 2026-04-18

Next.js で認証状態に応じてページを redirect する

Server Component や Route Handler でセッションの有無を確認し、認証済みユーザーをダッシュボードへ、未認証ユーザーをログインページへ redirect するパターンの例。

nextjsauthenticationrouting

対応バージョン

nextjs 15react 19

前提環境

Next.js App Router の Server Component と cookies() の基本を理解していること

概要

cookies() でセッション Cookie を確認し、redirect() で条件分岐するパターン。未認証ユーザーが保護ページにアクセスしたときログインページへ、ログイン済みユーザーがログインページにアクセスしたときダッシュボードへ、それぞれ redirect する。

インストール

# 追加インストールは不要

実装

認証済みユーザーのみアクセス可能なページ

// app/dashboard/page.tsx
import { cookies } from "next/headers";
import { redirect } from "next/navigation";

export default async function DashboardPage() {
  const cookieStore = await cookies();
  const sessionId = cookieStore.get("session_id");

  // 未認証ならログインページへ
  if (!sessionId) {
    redirect("/login");
  }

  return (
    <main className="mx-auto max-w-2xl p-8">
      <h1 className="mb-4 text-2xl font-bold">ダッシュボード</h1>
      <p className="text-gray-600">認証済みユーザーのみ表示されます。</p>
    </main>
  );
}

ログイン済みユーザーはリダイレクト(ログインページ)

// app/login/page.tsx
import { cookies } from "next/headers";
import { redirect } from "next/navigation";
import LoginForm from "@/components/LoginForm";

export default async function LoginPage() {
  const cookieStore = await cookies();
  const sessionId = cookieStore.get("session_id");

  // ログイン済みならダッシュボードへ
  if (sessionId) {
    redirect("/dashboard");
  }

  return (
    <main className="flex min-h-screen items-center justify-center">
      <LoginForm />
    </main>
  );
}

ログインフォーム(Client Component)

// components/LoginForm.tsx
"use client";

import { useState } from "react";
import { useRouter } from "next/navigation";

export default function LoginForm() {
  const router = useRouter();
  const [error, setError] = useState<string | null>(null);
  const [loading, setLoading] = useState(false);

  async function handleSubmit(e: React.FormEvent<HTMLFormElement>) {
    e.preventDefault();
    setLoading(true);
    setError(null);

    const fd = new FormData(e.currentTarget);
    const res = await fetch("/api/login", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({
        email: fd.get("email"),
        password: fd.get("password"),
      }),
    });

    if (res.ok) {
      router.push("/dashboard");
      router.refresh();
    } else {
      setError("メールアドレスまたはパスワードが正しくありません");
      setLoading(false);
    }
  }

  return (
    <form onSubmit={handleSubmit} className="w-80 space-y-4 rounded-lg border p-8">
      <h1 className="text-xl font-bold">ログイン</h1>
      {error && <p className="text-sm text-red-500">{error}</p>}
      <input
        name="email"
        type="email"
        placeholder="メールアドレス"
        required
        className="block w-full rounded border px-3 py-2 text-sm"
      />
      <input
        name="password"
        type="password"
        placeholder="パスワード"
        required
        className="block w-full rounded border px-3 py-2 text-sm"
      />
      <button
        type="submit"
        disabled={loading}
        className="w-full rounded bg-blue-600 py-2 text-sm text-white hover:bg-blue-700 disabled:opacity-50"
      >
        {loading ? "ログイン中…" : "ログイン"}
      </button>
    </form>
  );
}

セッション発行 API

// app/api/login/route.ts
import { cookies } from "next/headers";
import { NextResponse } from "next/server";

export async function POST(request: Request) {
  const body = await request.json() as { email: string; password: string };

  // 実際のアプリでは DB + bcrypt.compare() で検証する
  if (body.email !== "user@example.com" || body.password !== "password") {
    return NextResponse.json({ error: "認証失敗" }, { status: 401 });
  }

  const cookieStore = await cookies();
  cookieStore.set("session_id", crypto.randomUUID(), {
    httpOnly: true,
    secure: process.env.NODE_ENV === "production",
    sameSite: "lax",
    path: "/",
    maxAge: 60 * 60 * 24,
  });

  return NextResponse.json({ message: "ログイン成功" });
}

ログアウト API

// app/api/logout/route.ts
import { cookies } from "next/headers";
import { NextResponse } from "next/server";

export async function POST() {
  const cookieStore = await cookies();
  cookieStore.delete("session_id");
  return NextResponse.json({ message: "ログアウト成功" });
}

ポイント

  • Server Component から直接 redirect() を呼ぶと、コンポーネントのレンダリングを中断して即座にリダイレクトする。try/catch で囲まない限りコンポーネントの後続コードは実行されない
  • ログインページ側でも sessionId を確認してダッシュボードへ redirect することで、ログイン済みユーザーがログインページを再表示するのを防ぐ(二重ログイン防止)
  • redirect()next/navigation からインポートする。Server Component / layout / Route Handler から使用できる。Client Component では useRouter().push() を使う
  • ログイン成功後に router.push("/dashboard")router.refresh() を両方呼ぶ。refresh() でサーバーコンポーネントを再フェッチして Cookie を読み込ませることが重要
  • 実際のアプリでは sessionId.value を DB と照合してセッションの有効性を確認する。Cookie 値の存在だけで認証済みと判断するのはサンプル簡略化のため

注意点

nextjs-middleware-auth はミドルウェアで全リクエストを横断保護。nextjs-protected-layout は layout 単位の一括保護。nextjs-cookie-session は Cookie の発行・読み取り。これは個別の Server Component / Route Handler で認証状態を確認し条件分岐 redirect するパターンに特化。ログイン済みユーザーのログインページアクセスも含む。

関連サンプル