/login으로 리디렉션createBrowserClient로 로그인하면 세션이 localStorage에만 저장된다.
서버(Server Component, middleware)는 localStorage를 읽을 수 없기 때문에
항상 "로그인 안 된 상태"로 판단 → 무한 리디렉션 루프 발생.
해결 방법: 로그인 후 서버 콜백(/api/auth/callback)을 거쳐
세션을 쿠키에 저장해야 서버가 인증 상태를 읽을 수 있다.
src/
├── app/
│ ├── layout.tsx # RootLayout — ThemeProvider만
│ ├── page.tsx # 로그인 여부로 /tasks 또는 /login redirect
│ ├── (auth)/
│ │ └── login/
│ │ └── page.tsx # 로그인 폼
│ ├── api/
│ │ └── auth/
│ │ └── callback/
│ │ └── route.ts # ← 핵심! 세션을 쿠키에 저장
│ └── (dashboard)/
│ ├── layout.tsx # Header/Footer + 인증 체크
│ └── tasks/
│ └── page.tsx
├── components/
│ ├── common/
│ │ └── ThemeRegistry.tsx
│ └── layout/
│ ├── Header.tsx
│ └── Footer.tsx
└── lib/
└── supabase/
├── client.ts # 브라우저용
└── server.ts # 서버용 (async)
middleware.ts # 프로젝트 루트 — 세션 갱신만
middleware.ts (프로젝트 루트)세션 쿠키 갱신만 담당. redirect 로직 없음.
middleware에서 redirect하면 /login도 잡혀서 루프 발생하므로 제거.
export async function middleware(request: NextRequest) {
// supabase 클라이언트 생성 + getUser() 호출로 세션 갱신만
await supabase.auth.getUser();
return response;
}
src/app/api/auth/callback/route.ts ← 핵심로그인 성공 후 이 라우트를 거쳐야 세션이 쿠키에 저장된다.
export async function GET(request: NextRequest) {
const code = searchParams.get("code");
if (code) {
const supabase = await createClient();
await supabase.auth.exchangeCodeForSession(code);
}
return NextResponse.redirect(`${origin}/tasks`);
}
src/app/(auth)/login/page.tsx로그인 성공 후 router.push 대신 window.location.href 사용.
→ full reload로 서버가 새 쿠키를 읽도록 강제.
const { error } = await supabase.auth.signInWithPassword({ email, password });
if (!error) {
window.location.href = "/tasks"; // router.push 사용 금지
}
src/app/(dashboard)/layout.tsx인증 체크는 여기서만 담당.
const { data: { user } } = await supabase.auth.getUser();
if (!user) redirect("/login");
src/app/layout.tsxThemeProvider만. 인증 로직 없음.
이 파일에 인증 체크가 있으면 /login도 redirect 대상이 되어 루프 발생.
export default function RootLayout({ children }) {
return (
<html lang="ko">
<body>
<ThemeRegistry>{children}</ThemeRegistry>
</body>
</html>
);
}
클라이언트 signOut()은 localStorage만 지우고 서버 쿠키는 못 지움.
API route에서 서버 측 signOut을 호출해야 함.
// src/app/api/logout/route.ts
export async function POST() {
const supabase = await createClient();
await supabase.auth.signOut();
return NextResponse.redirect("/login");
}
// Header.tsx
const handleLogout = async () => {
await fetch("/api/logout", { method: "POST" });
window.location.href = "/login";
};
Authentication → URL Configuration
Site URL: http://localhost:3000
Redirect URLs: http://localhost:3000/api/auth/callback
| 문제 | 결과 | 해결 |
|---|---|---|
app/layout.tsx에 인증 체크 | /login도 redirect 대상이 되어 루프 | (dashboard)/layout.tsx로 이동 |
| middleware에서 redirect | /login 접근 시 루프 | middleware는 세션 갱신만 |
router.push("/tasks") 로 로그인 후 이동 | 서버가 세션 쿠키 못 읽음 | window.location.href 사용 |
클라이언트 signOut() 후 이동 | 서버 쿠키 미삭제 → 루프 | API route에서 서버 signOut |
| callback route 없이 로그인 | 세션이 localStorage에만 저장 | /api/auth/callback route 필수 |