
메인 랜딩 페이지에서 사용자의 로그인 상태에 따라 다른 페이지로 이동해야 합니다.
1️⃣ 로그인되지 않은 사용자는 /login 페이지로 이동
2️⃣ 로그인된 사용자는 마지막으로 속한 팀 페이지로 이동
3️⃣ 로그인된 사용자가 아직 팀에 속하지 않았다면 /noteam 페이지로 이동
❗ 하지만, 이 과정에서 다음과 같은 문제가 발생했습니다.
getUser() API를 호출하여 사용자 정보를 가져오는 과정에서, 랜딩 페이지가 로드될 때마다 API가 실행되면서 불필요한 요청이 많아지는 문제 발생이 문제를 해결하기 위해 Redux를 활용하여 전역적으로 로그인 상태를 관리하고,
React Query와 결합하여 불필요한 API 요청을 최소화하였습니다.
| 방법 | 장점 | 단점 |
|---|---|---|
| useState를 이용한 관리 | 간단한 로직 | 여러 컴포넌트에서 공유하기 어려움 |
| LocalStorage 활용 | 새로고침해도 상태 유지 가능 | XSS 공격 위험, 상태 변경 감지가 어려움 |
| React Context API | 비교적 가벼운 상태 관리 가능 | 대규모 애플리케이션에서는 성능 저하 가능 |
| Redux 사용 | 전역 상태 공유, 유지보수 용이 | 추가적인 코드 필요 |
✅ Redux를 사용하면 애플리케이션 전역에서 로그인 상태를 쉽게 공유할 수 있고, React Query를 함께 사용하여 API 요청을 최소화할 수 있습니다.
먼저, Redux를 사용하여 로그인 상태를 저장할 auth 슬라이스를 생성합니다.
// store/authSlice.ts
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
import { SignInResponse } from '@/app/types/AuthType';
interface AuthState {
user: Omit<SignInResponse['user'], 'updatedAt' | 'createdAt' | 'image'> | null;
accessToken: string;
refreshToken: string;
}
const initialState: AuthState = {
user: null,
accessToken: '',
refreshToken: '',
};
const authSlice = createSlice({
name: 'auth',
initialState,
reducers: {
setCredentials: (state, action: PayloadAction<SignInResponse>) => {
const { user, accessToken, refreshToken } = action.payload;
// 필요한 필드만 저장 (Omit을 통해 일부 필드 제외)
state.user = {
id: user.id,
email: user.email,
nickname: user.nickname,
teamId: user.teamId,
};
state.accessToken = accessToken ?? '';
state.refreshToken = refreshToken ?? '';
},
setAccessToken: (state, action: PayloadAction<string>) => {
state.accessToken = action.payload;
},
logout: (state) => {
state.user = null;
state.accessToken = '';
state.refreshToken = '';
},
},
});
export const { setAccessToken, setCredentials, logout } = authSlice.actions;
export default authSlice.reducer;
✔ Redux Toolkit을 사용하여 accessToken을 전역 상태로 관리
✔ setCredentials 액션을 통해 로그인 정보 저장 및 accessToken 업데이트
✔ 로그아웃 시 logout을 호출하여 모든 상태 초기화
이제 랜딩 페이지에서 Redux의 로그인 상태를 확인하고,
✅ 로그인되지 않은 경우 /login으로 이동
✅ 로그인된 경우 getUser()를 통해 팀 정보를 확인하고 해당 팀으로 이동
'use client';
import { useRouter } from 'next/navigation';
import { useSelector } from 'react-redux';
import { RootState } from '@/app/stores/store';
import { useQuery } from '@tanstack/react-query';
import getUser from '@/app/lib/user/getUser';
import { motion } from 'framer-motion';
export default function LandingHeader() {
const router = useRouter();
const { accessToken, user } = useSelector((state: RootState) => state.auth);
const { refetch } = useQuery({
queryKey: ['user'],
queryFn: getUser,
enabled: false, // API 요청을 자동으로 하지 않음
});
const handleStartClick = async () => {
if (!accessToken) {
router.push('/login'); // 로그인되지 않은 경우 로그인 페이지로 이동
return;
}
try {
if (user?.teamId) {
router.push(`/${user.teamId}`); // 이미 로그인된 경우 팀 페이지로 이동
return;
}
const { data: userData } = await refetch();
if (!userData || !userData.user.teamId) {
router.push('/noteam'); // 팀이 없는 경우 'noteam' 페이지로 이동
} else {
router.push(`/${userData.user.teamId}`); // 가장 최근 속한 팀으로 이동
}
} catch (error) {
router.push('/login'); // 에러 발생 시 로그인 페이지로 이동
}
};
return (
<motion.div
initial={{ opacity: 0, y: -50 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.8 }}
className="mt-14 flex h-[40rem] w-full flex-col items-center justify-between pb-8 tablet:h-[58.75rem] tablet:pb-[10.5rem] xl:h-[67.5rem] xl:pb-[11.25rem]"
>
<button
onClick={handleStartClick}
className="cursor-pointer rounded-[2rem] bg-gradient-to-r from-brand-primary to-brand-tertiary px-[8rem] py-3 text-base font-semibold text-white tablet:px-[8.9375rem]"
>
지금 시작하기
</button>
</motion.div>
);
}
✅ Redux를 사용하여 로그인 상태를 전역에서 쉽게 관리 가능
✅ React Query와 결합하여 API 요청을 최소화 (enabled: false 설정)
✅ 로그인 상태를 기반으로 동적 라우팅을 구현하여 사용자 경험 개선
| 적용 전 | 적용 후 |
|---|---|
| ✅ 로그인 상태를 useState로 관리하여 공유 어려움 | ✅ Redux를 활용한 전역 상태 관리로 해결 |
| ❌ 페이지 로드 시마다 getUser() API 호출 | ✅ Redux 상태 활용하여 불필요한 요청 방지 |
| ❌ 로그인 후에도 이동이 지연됨 | ✅ 즉시 로그인 상태를 확인하여 경로 이동 최적화 |
Redux를 활용하면 로그인 상태를 전역적으로 관리할 수 있고,
React Query를 활용하여 불필요한 API 요청을 줄이며,
사용자의 상태에 따라 동적인 페이지 이동을 최적화할 수 있었습니다.