第124章:練習:/dashboard をログイン必須にする導線🧱🔐
この章は「/dashboard に入ろうとしたら、ログインしてない人は /login に飛ばす」っていう、超よく使う“守り方”を体験する回だよ〜!😆✨ (本物の認証はまだ先でOK!今日は「導線」と「入口ガード」を作る練習だよ🧸)
🎯 今日のゴール
-
/dashboardにアクセスしたら…- ✅ ログイン済み(クッキーあり)→そのまま表示🎉
- ❌ 未ログイン(クッキーなし)→
/loginにリダイレクト🚪➡️🔑
-
ログインしたら、元のページ(/dashboard)へ戻れるようにする🧭✨
🗺️ 導線の図解(全体の流れ)✨
✅ まず作るもの(ファイル一覧)📁✨
proxy.ts(旧 middleware.ts。最新ではproxy.tsが推奨だよ〜) (Next.js)app/login/page.tsx(ログイン画面)app/dashboard/page.tsx(保護したい画面)app/api/demo-login/route.ts(ログイン:クッキーを付ける)app/api/demo-logout/route.ts(ログアウト:クッキー消す)components/LogoutButton.tsx(ログアウトボタン)
1) まずは「ログイン画面」と「ダッシュボード」を作る🏠🔑
✅ app/login/page.tsx
'use client'
import { useRouter, useSearchParams } from 'next/navigation'
export default function LoginPage() {
const router = useRouter()
const searchParams = useSearchParams()
const from = searchParams.get('from') ?? '/dashboard'
const onLogin = async () => {
await fetch('/api/demo-login', { method: 'POST' })
router.push(from)
router.refresh()
}
return (
<main style={{ padding: 24 }}>
<h1 style={{ fontSize: 24, fontWeight: 700 }}>ログイン🔑✨</h1>
<p style={{ marginTop: 8 }}>ここは練習用だよ〜!ボタン押したらログイン扱いにするね😺</p>
<button
onClick={onLogin}
style={{
marginTop: 16,
padding: '10px 14px',
borderRadius: 10,
border: '1px solid #ddd',
cursor: 'pointer',
}}
>
ログインする🎉
</button>
</main>
)
}
✅ app/dashboard/page.tsx
import LogoutButton from '@/components/LogoutButton'
export default function DashboardPage() {
return (
<main style={{ padding: 24 }}>
<h1 style={{ fontSize: 24, fontWeight: 700 }}>Dashboard 📊✨</h1>
<p style={{ marginTop: 8 }}>ここはログインした人だけが見れるページだよ〜🔐</p>
<div style={{ marginTop: 16 }}>
<LogoutButton />
</div>
</main>
)
}
✅ components/LogoutButton.tsx
'use client'
import { useRouter } from 'next/navigation'
export default function LogoutButton() {
const router = useRouter()
const onLogout = async () => {
await fetch('/api/demo-logout', { method: 'POST' })
router.push('/login')
router.refresh()
}
return (
<button
onClick={onLogout}
style={{
padding: '10px 14px',
borderRadius: 10,
border: '1px solid #ddd',
cursor: 'pointer',
}}
>
ログアウトする🚪🍪
</button>
)
}
2) ログイン/ログアウトAPI(クッキーを付ける🍪✨)
Next.js は Route Handler / Server Action から cookies() でクッキーを set/delete できるよ〜! (Next.js)
✅ app/api/demo-login/route.ts
import { cookies } from 'next/headers'
import { NextResponse } from 'next/server'
export async function POST() {
const cookieStore = await cookies()
cookieStore.set('demo_session', 'ok', {
httpOnly: true,
sameSite: 'lax',
path: '/',
maxAge: 60 * 60, // 1時間(練習用)
})
return NextResponse.json({ ok: true })
}
✅ app/api/demo-logout/route.ts
import { cookies } from 'next/headers'
import { NextResponse } from 'next/server'
export async function POST() {
const cookieStore = await cookies()
cookieStore.delete('demo_session')
return NextResponse.json({ ok: true })
}
3) いよいよ入口ガード! proxy.ts を作る🧤🚦
💡 ポイント: proxy.ts は「ページが描画される前」に先に動ける“門番”だよ〜!🧱
(昔は middleware.ts って呼ばれてたけど、最新では proxy.ts が推奨になってるよ) (Next.js)
✅ proxy.ts(プロジェクトのルート直下)
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'
export function proxy(request: NextRequest) {
const hasSession = request.cookies.has('demo_session')
if (hasSession) {
return NextResponse.next()
}
// 未ログインなら /login へ(from に元のパスを入れておく)
const url = request.nextUrl.clone()
url.pathname = '/login'
url.searchParams.set('from', request.nextUrl.pathname)
return NextResponse.redirect(url)
}
export const config = {
matcher: ['/dashboard/:path*'],
}
matcher: ['/dashboard/:path*']で /dashboard とその下全部(例:/dashboard/settings)に適用できるよ🧭 (Next.js)request.cookies.has(...)でクッキーの有無チェック🍪 (Next.js)NextResponse.redirect(...)でリダイレクト🚪 (Next.js)
✅ 動作チェック(ここ楽しいやつ😆🎮)
-
開発サーバー起動
npm run dev -
ブラウザで
http://localhost:3000/dashboardを開く🌈- クッキーない →
/login?from=/dashboardに飛ぶ🚪✨
- クッキーない →
-
ログインボタン押す🎉
/api/demo-loginがクッキーを付ける🍪/dashboardに戻る🧭
-
ログアウトボタン押す🚪
- クッキー消える🍪❌
/loginに戻る🔁
😵 よくあるハマり(先に潰す🥊✨)
-
proxy.tsを置く場所が違う →app/と同じ階層(プロジェクトのルート)に置くよ! (Next.js) -
matcherを広くしすぎて、全部ログイン必須になっちゃう → まずは['/dashboard/:path*']だけにして安全運転が◎🧸 -
ログイン後に戻る先がわからない →
fromクエリを使うのがいちばん簡単だよ〜🧭✨
🎁 できたこと(えらい!👏🥰)
- 「ページを表示する前」に、入口でログイン判定できた🧤🔐
- 未ログインなら
/loginに誘導できた🚪 - ログイン後に元の場所へ戻す導線も作れた🧭✨
この導線ができると、次に“本物の認証”を入れる時も、置き換えるだけで一気に強くなるよ〜!💪🔥