📘 Aura_AI Day 9 — Zustand로 인증 상태 통합
(완전 A~Z · 코드 · 문법 · 데이터 흐름까지 전부 설명)
안녕하세요.
오늘은 새로운 기능을 추가하는 날이 아니라,
이미 구현된 인증 기능을 “제대로 쓰기 위한 구조”를 설계한 날입니다.
Day 7–8에서
- Google OAuth 로그인
- 이메일/비밀번호 회원가입 및 로그인
기능은 모두 완성되었습니다.
하지만 **“로그인 상태를 어떻게 앱 전체에서 다룰 것인가?”**라는 문제는 남아 있었습니다.
그래서 Day 9의 목표는 명확했습니다.
0️⃣ 오늘 왜 이 작업을 했나요? (목적 선언)
기존 앱에서는 로그인 상태를 화면마다 다르게 판단하고 있었습니다.
- 어떤 컴포넌트는 useSession()을 직접 사용하고
- 어떤 화면은 잠깐 로그아웃처럼 보였다가 다시 바뀌고
- Google 로그인과 ID/PW 로그인을 UI에서 계속 구분해야 했습니다.
그래서 오늘 한 작업은 다음 세 가지를 해결하기 위한 것이었습니다.
✅ 로그인 상태를 한 곳에서만 관리
✅ OAuth / ID 로그인 모두 완전히 동일한 방식으로 처리
✅ 새로고침해도 UI가 안정적으로 유지
이를 위해 Zustand를 도입했습니다.
1️⃣ Zustand란 무엇인가요?
Zustand는 전역 상태 관리 라이브러리입니다.
“앱 전체에서 공유해야 하는 데이터”를 한 곳에 저장하고,
모든 컴포넌트가 그 데이터를 동일하게 바라보게 합니다.
로그인 상태는 대표적인 전역 상태입니다.
- 로그인 여부
- 로그인한 유저 정보
- 세션 로딩 중인지 여부
이것을 컴포넌트마다 따로 관리하면
상태 불일치와 중복 로직이 발생합니다.
그래서 전역 스토어가 필요합니다.
❓ 왜 React Context 대신 Zustand인가요?
- Context는 값이 자주 바뀌면 리렌더링 범위가 커짐
- 인증 상태는 앱 전반에 영향을 주므로 경량 스토어가 적합
- Zustand는 설정이 단순하고 실무 사용 빈도가 높음
2️⃣ 오늘 새로 추가한 파일들 (A~Z 순서)
✅ 2-1. AuthUser 모델 (유저 타입 통합)
export interface AuthUser {
id: string;
name: string;
email: string;
image?: string | null;
provider: "google" | "credentials";
}
🔍 설명
- interface는 객체의 형태를 정의하는 TypeScript 문법입니다.
- Google 로그인과 ID 로그인은 구조가 다르기 때문에
앱 내부에서 사용할 “표준 유저 모델”이 필요했습니다.
✅ 효과
“이 앱에서 유저는 항상 이 모양이다.”
UI, 스토어, API 연동 모두에서
유저 구조를 의심하지 않고 바로 사용할 수 있습니다.
✅ 2-2. authStore.ts (Zustand 인증 스토어)
import { create } from "zustand";
import { AuthUser } from "@/types/auth";
interface AuthState {
user: AuthUser | null;
isAuthenticated: boolean;
isLoading: boolean;
setUser: (user: AuthUser | null) => void;
setLoading: (loading: boolean) => void;
clearUser: () => void;
}
export const useAuthStore = create<AuthState>((set) => ({
user: null,
isAuthenticated: false,
isLoading: true,
setUser: (user) =>
set({
user,
isAuthenticated: !!user,
isLoading: false,
}),
clearUser: () =>
set({
user: null,
isAuthenticated: false,
isLoading: false,
}),
setLoading: (loading) => set({ isLoading: loading }),
}));
🔍 핵심 포인트
- create() : Zustand 스토어 생성
- isAuthenticated를 직접 관리하지 않고
user 존재 여부로 자동 계산 - isLoading은 새로고침 시 UI 깜빡임 방지용
✅ 2-3. AuthSync.tsx (Auth.js ↔ Zustand 브릿지)
"use client";
import { useEffect } from "react";
import { useSession } from "next-auth/react";
import { useAuthStore } from "@/store/authStore";
import { AuthUser } from "@/types/auth";
export default function AuthSync() {
const { data: session, status } = useSession();
const { setUser, clearUser, setLoading } = useAuthStore();
useEffect(() => {
if (status === "loading") {
setLoading(true);
return;
}
if (!session?.user) {
clearUser();
return;
}
const user: AuthUser = {
id: session.user.id!,
name: session.user.name!,
email: session.user.email!,
image: session.user.image,
provider: session.user.provider as "google" | "credentials",
};
setUser(user);
}, [session, status, setUser, clearUser, setLoading]);
return null;
}
🔍 왜 필요한가요?
- useSession()은 React Hook → 컴포넌트 안에서만 사용 가능
- Zustand는 전역 스토어
- 둘을 직접 연결할 수 없기 때문에 중간 브릿지 컴포넌트가 필요
✅ 2-4. next-auth.d.ts (타입 확장)
import "next-auth";
declare module "next-auth" {
interface Session {
user?: {
id?: string;
name?: string | null;
email?: string | null;
image?: string | null;
provider?: "google" | "credentials";
};
}
}
declare module "next-auth/jwt" {
interface JWT {
id?: string;
provider?: "google" | "credentials";
}
}
🔍 이유
NextAuth 기본 타입에는 id, provider가 없습니다.
TypeScript 에러를 타입 확장으로 정식 해결했습니다.
3️⃣ 수정된 파일들
✅ providers.tsx
<SessionProvider>
<ThemeProvider ...>
<AuthSync />
{children}
</ThemeProvider>
</SessionProvider>
→ 앱 시작 시 항상 인증 상태 동기화
✅ auth.ts (JWT / Session 콜백)
async jwt({ token, user, account }) {
if (user) token.id = user.id;
if (account?.provider) token.provider = account.provider;
return token;
}
async session({ session, token }) {
if (session.user) {
session.user.id = token.id as string;
session.user.provider = token.provider as "google" | "credentials";
}
return session;
}
✅ Header / page.tsx
const { isAuthenticated, isLoading } = useAuthStore();
if (isLoading) return null;
→ UI는 이제 Zustand만 신뢰
4️⃣ 실제로 겪은 오류 정리
- AuthSync import 누락
- provider 타입 에러 → 타입 확장
- Header에 useSession 잔존 → Zustand 기준 통일
5️⃣ 전체 데이터 흐름 (A → Z)
로그인 성공
→ Auth.js 세션 생성
→ auth.ts에서 JWT에 id/provider 저장
→ AuthSync가 세션 감지
→ Zustand에 user 저장
→ UI는 Zustand만 사용
6️⃣ 왜 이 방법을 선택했나요?
- UI마다 useSession 직접 사용 ❌
- 분기 로직 중복 ❌
- 테스트/유지보수 어려움 ❌
현재 구조:
- 한 번만 동기화
- 단일 기준
- 이후 API 연동 매우 쉬움
✅ Day 9 최종 체크리스트
- AuthUser 모델 정의
- Zustand 인증 스토어
- AuthSync 브릿지
- Provider 연결
- UI Zustand 기준 통일
- 타입 확장
- 오류 해결
'챗봇 공부 노트' 카테고리의 다른 글
| [11편] API 구조 & 데이터 계약(Contract) 고정 (0) | 2026.01.26 |
|---|---|
| [10편] FastAPI 백엔드 분리 & 프론트엔드 연동 (1) | 2026.01.26 |
| [8편] OAuth vs 자체 회원가입 — 서비스에 맞는 인증 전략 (0) | 2026.01.23 |
| [7편] Google OAuth 인증 체계 구축 (1) | 2026.01.21 |
| [6편] MyPage분리 + 채팅 검색 기능 추가 (0) | 2026.01.20 |