Skip to content

Refactor(client): 라우팅 구조 개선 및 접근 제어 로직 리팩토링#211

Open
jyeon03 wants to merge 4 commits intodevelopfrom
refactor/routing-structure/#210
Open

Refactor(client): 라우팅 구조 개선 및 접근 제어 로직 리팩토링#211
jyeon03 wants to merge 4 commits intodevelopfrom
refactor/routing-structure/#210

Conversation

@jyeon03
Copy link
Collaborator

@jyeon03 jyeon03 commented Feb 8, 2026

📌 Summary

분산되어 있던 라우팅 구조를 중앙에서 관리하도록 단일화하고, 불완전했던 접근 제어 로직을 개선하여 유지보수성과 확장성 을 향상시키는 리팩토링 작업을 진행했습니다!!!!!

📚 Tasks

  • 라우트 설정 파일을 private-route.ts, public-route.ts에서 routes/index.ts로 통합
  • 기존 AuthGuard를 PrivateRouteGuard와 PublicRouteGuard로 분리하여 역할과 책임 명확화
  • 메인 라우터(router.tsx)가 중앙화된 routes 배열을 동적으로 읽어 처리하도록 리팩토링
  • 더 이상 사용되지 않는 구버전 라우트 파일 삭제

🔍 Describe
왜 이 리팩토링이 필요했나??

  1. 단일 진실 공급원(SSoT) 원칙 위배
  • 라우트 정보가 private-route.ts와 public-route.ts로 분리되어 전체 구조 파악이 어려웠고, 인증 정책 변경 시 여러 파일을 수정해야 하는 번거로움이 있었습니다.
  1. 단일 책임 원칙(SRP) 위배
  • 기존 AuthGuard는 미인증 사용자 차단인증된 사용자 처리라는 여러 책임을 가지려 했으나, 후자를 guard에서 제대로 처리하지 못해 로직에 허점이 존재했습니다.

어떻게 재설계했나??

  1. 라우트 설정 중앙화 (routes/index.ts)
  • 모든 경로 설정을 routes/index.ts 단일 파일로 통합하여 SSoT을 확보했습니다. 각 경로에 auth: true 메타데이터를 추가하여 인증 필요 여부를 선언적으로 명시하도록 변경했습니다.

  • 변경 후

import { RouteObject } from 'react-router';

// RouteObject에 auth 속성을 추가한 커스텀 타입 정의
type AuthRouteObject = RouteObject & {
   auth?: boolean;
};

// 모든 경로를 하나의 배열에서 관리
export const routes: AuthRouteObject[] = [
   // Private Routes
   {
     path: PATH.ALL_MEMO,
     Component: AllMemoPage,
     auth: true, // "인증 필요" 메타데이터
   },
   // Public Routes
   {
     path: PATH.LOGIN,
     Component: LoginPage,
   },
 ];
  1. 접근 제어 로직 분리 (auth-guard.tsx)
  • 단일 책임 원칙에 따라, 하나의 복잡한 AuthGuard를 두 개의 명확한 책임을 가진 PrivateRouteGuard와 PublicRouteGuard로 분리했습니다.

  • 변경 전

 const AuthGuard = ({ children }) => {
   const accessToken = getAccessToken();

   if (!accessToken) {
     // 미인증 사용자를 막는 로직만 존재
     return <Navigate to={PATH.LANDING} replace />;
   }

   return <>{children}</>;
 };
  • 변경 후
 // Private 경로 보호
 export const PrivateRouteGuard = ({ children }) => {
   const accessToken = getAccessToken();
   if (!accessToken) {
     return <Navigate to={PATH.LOGIN} replace />;
   }
   return <>{children}</>;
 };

 // Public 경로 처리
 export const PublicRouteGuard = ({ children }) => {
   const accessToken = getAccessToken();
   const { pathname } = useLocation();
   const publicOnlyPaths: string[] = [PATH.LOGIN, PATH.LOGIN_CALLBACK];
 
   if (accessToken && publicOnlyPaths.includes(pathname)) {
     // 기인증 사용자는 메인 페이지로 이동
     return <Navigate to={PATH.ALL_MEMO} replace />;
   }
   return <>{children}</>;
  };
  1. 동적 라우터 구성 (router.tsx)
  • 메인 라우터가 중앙화된 routes 배열을 동적으로 필터링하여 Public/Private 경로 그룹을 자동으로 구성하도록 수정했습니다.

  • 변경 전

import { privateRoutes } from './routes/private-route';
import { publicRoutes } from './routes/public-route';

export const router = createBrowserRouter([
   {
     Component: GlobalLayout,
     children: [
       {
         Component: PublicLayout,
         children: publicRoutes, 
       },
       {
         Component: PrivateLayoutWithGuard,
         children: privateRoutes, 
       },
     ],
   },
]);
  • 변경 후
import { routes } from './routes'; // 중앙화된 단일 라우트 import

export const router = createBrowserRouter([
   {
     Component: GlobalLayout,
     children: [
       {
         Component: GuardedPublicLayout,
         // 'auth' 속성이 없는 경로만 동적으로 필터링
         children: routes.filter((r) => !r.auth),
       },
       {
         Component: GuardedPrivateLayout,
         // 'auth' 속성이 있는 경로만 동적으로 필터링
         children: routes.filter((r) => r.auth),
       },
     ],
   },
]);

👀 To Reviewer

  • 장기적인 관점에서 새로운 아키텍처가 프로젝트의 확장성과 안정성에 기여할지에 대한 의견을 주시면 감사하겠습니다.
  • 현재 기획상 필요하진 않지만, state={{ from: location }} 패턴을 사용하면 로그인 후 원래 접근하려던 경로로 복귀시키는 UX를 구현해보면 어떨까싶어요! 추후 딥링크 대응이나 기획 변경 가능성을 고려하면 Guard에서 이 정보를 함께 전달하는 구조도 한 가지 방법일 것 같다는 생각이 들었습니당 🥸

@jyeon03 jyeon03 requested a review from a team as a code owner February 8, 2026 16:27
@jyeon03 jyeon03 linked an issue Feb 8, 2026 that may be closed by this pull request
@jyeon03 jyeon03 requested review from jm8468, jogpfls and twossu and removed request for a team February 8, 2026 16:27
@github-actions github-actions bot added 🔨 Refactor 코드 구조 개선 🐈‍⬛ 백지연 웹 37기 백지연 labels Feb 8, 2026
@github-actions
Copy link

github-actions bot commented Feb 8, 2026

🎨 Storybook 배포 완료

PR 작성자: @jyeon03

🔗 배포된 Storybook 보기

@jogpfls jogpfls changed the title Refactor[client]: 라우팅 구조 개선 및 접근 제어 로직 리팩토링 Refactor(client): 라우팅 구조 개선 및 접근 제어 로직 리팩토링 Feb 9, 2026
Copy link
Collaborator

@jogpfls jogpfls left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

크으 ~ 리팩토링의 첫 PR 영광스럽네요
코멘트 몇 개 남겼는데 확인 부탁드릴게요 !! 라우팅 관련해서 함께 이야기해보고 더 좋은 구조 함께 고민해보면 좋을 것 같아요 !!

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

저는 publicRoutes, privateRoutes로 라우트를 분리하는 방식이 유지보수성과 가독성 측면에서 더 낫다고 생각해요 !!

인증 필요/불필요라는 기준이 명확한 도메인 규칙이기 때문에 오히려 지금처럼 라우트를 한 배열에 섞어두면 규모가 커질수록 분류 비용과 실수 가능성이 크다고 생각합니다 !

특히 새로운 페이지 추가 시에 이 페이지가 인증이 필요한가?를 판단하고 그 결과에 따라 private/public 중 한 곳에만 넣으면 되기 때문에 변경 범위가 작고 리뷰 포인트도 명확해진다고 생각하는데 지연님의 생각은 어떨까요 ?

또한 SSoT 위배 라고 말씀해주셨는데 분리 자체는 중복 정의가 아니라 책임 분리에 가깝다고 생각해요 !
SSoT가 실제로 깨지는 케이스는 이 페이지가 private인지 여부와 같은 동일한 규칙이 routes 파일/guard/메뉴 매핑 등 여러 곳에서 중복으로 관리되어 불일치가 생길 때인데 현재 구조는 접근 정책을 privateRoutes/publicRoutes라는 한 기준으로 모아두는 형태라 오히려 정책을 추적하기 쉽다고 생각합니다 !!

Component: PublicLayout,
children: publicRoutes,
Component: GuardedPublicLayout,
children: routes.filter((r) => !r.auth),
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이런식으로 filter 함수를 사용하면 가독성이 떨어질 것 같아요 ! routes 파일을 다시 분리하게 된다면 해결될 문제 같아요 !

Comment on lines +29 to +37
const { pathname } = useLocation();

const publicOnlyPaths: string[] = [
PATH.LANDING,
PATH.LOGIN,
PATH.LOGIN_CALLBACK,
];

if (accessToken && publicOnlyPaths.includes(pathname)) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

const GuardedPrivateLayout = () => ( 
  <PrivateRouteGuard> 
    <PrivateLayout />
  </PrivateRouteGuard>
);

PublicRouteGuard를 PublicLayout 전체에 감싸면
PublicLayout 아래에 있는 모든 라우트가 public 영역이 되고 있기 때문에

현재 pathname이 publicOnlyPaths에 해당하나를 검사할 이유가 없을 것 같아요 !!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

🐈‍⬛ 백지연 웹 37기 백지연 🔨 Refactor 코드 구조 개선

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Refactor] 라우터 단일화 리팩토링

2 participants