들어가며
권한 관리를 구현하게 되었습니다. 확장성, 단일 책임, 시점 이동 최소화 3가지 원칙에 집중해서 설계했습니다.
핵심 설계 원칙
1. 컴포지션 패턴으로 확장성 확보
/**
* 접근 권한이 없는 경우 메인 페이지로 리다이렉트하는 기본 보호 라우트
* @param isAccessible 접근 권한 여부
* @param children 자식 컴포넌트
*/
// 기본 protected route - 단일 책임
export const ProtectedRoute = ({
isAccessible,
children,
}: ProtectedRouteProps) => {
if (!isAccessible) {
return <Navigate to={ROUTE_PATHS.root} replace />;
}
return children;
};
// 역할 기반 - protected route 재사용
export const RoleProtectedRoute = ({
requiredRole,
children,
}: RoleProtectedRouteProps) => {
const { user } = useUser();
return (
<ProtectedRoute isAccessible={user?.role === requiredRole}>
{children}
</ProtectedRoute>
);
};
// 권한 기반 - protected route 재사용
export const PermissionProtectedRoute = ({
requiredPermission,
children,
}: PermissionProtectedRouteProps) => {
const { user } = useUser();
return (
<ProtectedRoute isAccessible={getHasPermission(user, requiredPermission)}>
{children}
</ProtectedRoute>
);
};
// 컴포넌트 단위 권한 제한
export const RequirePermission = ({
permission,
children,
}: RequirePermissionProps) => {
const { user } = useUser();
return getHasPermission(user, permission) ? children : null;
};
새로운 권한 타입이 추가되어도 기본 ProtectedRoute만 재사용하면 됩니다. 리다이렉트 로직 변경도 한 곳에서만 수정 가능합니다.
useUser를 외부에서 주입하도록 해서 완벽한 단일 책임을 구현하고 싶었으나, 그러면 바깥에서 훅을 선언해 주입해줘야 하므로 사용성이 떨어집니다. 어쩔 수 없는 트레이드오프인 것 같습니다.
2. 선언적 라우터 설정으로 가독성 챙기기
export const router = createBrowserRouter([
{
path: ROUTE_PATHS.root,
Component: AdminLayout,
children: [
{
path: ROUTE_PATHS.pageA,
element: (
<PermissionProtectedRoute requiredPermission="page_a_access">
<PageA />
</PermissionProtectedRoute>
),
},
{
path: ROUTE_PATHS.pageB,
element: (
<RoleProtectedRoute requiredRole="admin">
<PageB />
</RoleProtectedRoute>
),
},
],
},
]);
라우터 설정만 봐도 각 페이지의 권한 요구사항을 즉시 파악할 수 있습니다. 권한 정책이 코드에서 바로 드러납니다.
JSX단에서 사용 가능
react router, tanstack table의 accessor처럼 컴포넌트 단위로 조건 제한이 필요한 상황에서 용이합니다
React Router v7
// 권한 체크가 라우터 레벨에서 선언적으로 처리됨
element: (
<PermissionProtectedRoute requiredPermission="finance_access">
<Finance />
</PermissionProtectedRoute>
)
TanStack Table
// 테이블 컬럼별 권한 제어
{
id: 'actions',
header: '작업',
cell: ({ row }) => (
<RequirePermission permission="user_management">
<UserActionButtons user={row.original} />
</RequirePermission>
),
}
덕분에 권한 체크를 컴포넌트 외부에서 처리할 수 있습니다. 관심사 분리가 자연스럽게 이루어집니다.
결과
- 확장성: 새로운 권한 타입 추가 시 기본 패턴 재사용
- 가독성: 권한 요구사항이 코드에서 바로 드러남
- 시점 이동 최소화: 관련 정보가 한 곳에 모여있음
'<frontend>' 카테고리의 다른 글
| 클라이언트 데이터 정규화 (15) | 2025.11.15 |
|---|---|
| 주석대신 순수함수로 TODO적기 (20) | 2025.10.19 |
| Single Source of Truth Config Pattern (16) | 2025.09.21 |
| 프론트엔드 검색 알고리즘 최적화(Feat. Trie) (12) | 2025.08.09 |
| pretendard에 숫자 고정폭 적용하기 (4) | 2024.10.27 |