사용자는 항상 내가 원하는 대로 동작하지 않는다.
내가 "로그인 버튼을 눌러서 로그인 하고 마이페이지는 드롭다운 내의 메뉴로 접근하세요" 라는 흐름을 만들어 놨어도 사용자가 주소창에 경로를 직접 입력해서 특정 페이지(ex.마이페이지)로 들어올 수 있다.

UI상에서는 숨겨져 있거나 버튼이 막혀있고, 백엔드에서 막아주고 있는 상태여도
URL 직접 접근은 언제든 가능하기 때문에 해당 문제들이 발생한다.
그래서 "이 경로는 로그인한 사용자만 접근 가능" 같은 규칙을 라우팅 레벨에서 중앙집중적으로 차단 하기위해 Route Guard를 사용한다.
액션 가드(Action Guard)
장점: 특정 기능만 보호 가능, UX가 직관적
단점: 라우트 자체는 접근 가능(페이지 단에서 한 번 더 막아야 완벽)
라우트 가드(Route Guard)
<ProtectedRoute><Page/></ProtectedRoute> 형태로 감싸서 진입을 막음장점: 페이지 단에서 확실하게 막힘
하나만 사용하는 경우도 있지만, 결국에는 UX를 위해서는 프로젝트를 진행할때는 항상 2가지 전부 사용을 하는 모습이다.
isAuthenticated = false 일 수 있다.bootstrap() 으로 principal 복구 시도 후 판정해야 안정적<Navigate /> 사용 (SPA 유지)<ProtectedRoute><Page/></ProtectedRoute> 형태로 사용하기 때문에
function ProtectedRoute({ children }: { children: React.ReactNode })
<div /> , <MyComp /> const loc = useLocation();
// 인증 boolean
const isAuthenticated = usePrincipalState((s) => s.isAuthenticated);
// 복구 시도 시 필요
const bootstrap = usePrincipalState((s) => s.bootstrap);
const [ready, setReady] = useState(false);
useEffect(() => {
(async () => {
await bootstrap(); // 토큰 존재 시 principal 복구 시도
setReady(true);
})();
}, []); // 의존성 배열 없이 마운트 1회만
// 로딩중 화면
if (!ready) return <div style={{ padding: 16 }}>로딩중...</div>;
// 인증되지 않은 상황에서는 로그인 화면으로 넘기고, 로그인 후 원래 본인이 접근하려던 곳으로 보내줌
if (!isAuthenticated) {
return <Navigate to="/signin" replace state={{ from: loc }} />;
}
return <>{children}</>;
Page가 <Outlet /> 을 렌더한다면, 상위에서 한 번만 감싸면 하위 라우트가 전부 보호된다.
<Route
element={
<ProtectedRoute>
<Page />
</ProtectedRoute>
}
>
// ...여기있는 라우트들은 보호 대상
</Route>
뒤로가기 등으로 로그인 페이지에 다시 접근 가능한 상황에서는
로그인 페이지 자체에서 "로그인 상태면 즉시 튕기기" 처리를 하면 된다.
const isAuthenticated = usePrincipalState((s) => s.isAuthenticated);
useEffect(() => {
if (isAuthenticated) {
navigate(fromPath, { replace: true });
}
}, [isAuthenticated]);
/signin으로 보낼 때 state.from 저장 → 로그인 성공 시 navigate(from, { replace:true })replace:true로 히스토리에서 로그인 페이지 제거(뒤로가면 로그인창 재등장 방지)