概要
cookies().delete() で httpOnly session cookie を削除することでログアウトを実現する。ログアウト後のリダイレクト・Client Component からの呼び出し・複数 Cookie のクリアパターンを示す。
インストール
# 追加インストールは不要
実装
ログアウト Route Handler
// app/api/logout/route.ts
import { cookies } from "next/headers";
import { NextResponse } from "next/server";
export async function POST() {
const cookieStore = await cookies();
// session cookie を削除
cookieStore.delete("session_id");
// ログインページへリダイレクト
return NextResponse.redirect(new URL("/login", process.env.NEXT_PUBLIC_BASE_URL ?? "http://localhost:3000"));
}
Server Component でのセッション確認
// app/dashboard/page.tsx
import { cookies } from "next/headers";
import { redirect } from "next/navigation";
import { LogoutButton } from "@/components/LogoutButton";
export default async function DashboardPage() {
const cookieStore = await cookies();
const sessionId = cookieStore.get("session_id");
// セッションがなければログインページへ
if (!sessionId) {
redirect("/login");
}
return (
<main className="p-8">
<h1 className="mb-4 text-2xl font-bold">ダッシュボード</h1>
<p className="mb-6 text-sm text-gray-600">
セッション ID: {sessionId.value}
</p>
<LogoutButton />
</main>
);
}
ログアウトボタン(Client Component)
// components/LogoutButton.tsx
"use client";
import { useRouter } from "next/navigation";
import { useState } from "react";
export function LogoutButton() {
const router = useRouter();
const [loading, setLoading] = useState(false);
async function handleLogout() {
setLoading(true);
await fetch("/api/logout", { method: "POST" });
router.push("/login");
router.refresh();
}
return (
<button
onClick={handleLogout}
disabled={loading}
className="rounded bg-red-600 px-4 py-2 text-sm text-white hover:bg-red-700 disabled:opacity-50"
>
{loading ? "ログアウト中..." : "ログアウト"}
</button>
);
}
ログインページ(Cookie の発行)
// 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 sessionId = crypto.randomUUID();
const cookieStore = await cookies();
cookieStore.set("session_id", sessionId, {
httpOnly: true,
secure: process.env.NODE_ENV === "production",
sameSite: "lax",
path: "/",
maxAge: 60 * 60 * 24, // 24時間
});
return NextResponse.json({ message: "ログイン成功" });
}
複数 Cookie を一括クリアする場合
// app/api/logout/route.ts(複数 Cookie 版)
import { cookies } from "next/headers";
import { NextResponse } from "next/server";
const SESSION_COOKIES = ["session_id", "csrf_token", "remember_me"] as const;
export async function POST() {
const cookieStore = await cookies();
for (const name of SESSION_COOKIES) {
cookieStore.delete(name);
}
return NextResponse.json({ message: "ログアウトしました" });
}
ポイント
cookieStore.delete("session_id")で Cookie を削除する。Set-Cookie: session_id=; Max-Age=0と同等のレスポンスが返る- Client Component から
fetch("/api/logout", { method: "POST" })を呼んだ後にrouter.refresh()を呼ぶことで Server Component のセッション確認が再実行され、UI が最新状態に更新される router.push("/login")とrouter.refresh()は両方呼ぶ。pushだけではページ遷移するが Server Component のキャッシュが残る場合があるnextjs-cookie-sessionとの使い分け: Cookie の発行・読み書きの仕組みはそちらを参照。このサンプルは「ログアウト導線(削除 + リダイレクト + 再検証)」に特化- 実際のアプリでは Cookie 削除と合わせてサーバー側の DB セッションも無効化する(例: セッションテーブルから該当レコードを削除)