[Groupware] React : Route (라우팅 설정)

2025. 6. 4. 23:12·Project/Frontend

📝 개요

라우팅은 사용자가 URL 을 이동할 떄 그에 맞는 화면 컴포넌트를 보여주는 구조를 의미한다. SPA(Single Page Application) 인 React 에서는 실제로 페이지를 새로고침하지 않고 URL 경로에 따라 필요한 컴포넌트만 렌더링하거나 교체하는 방식으로 동작한다.

 

React 에서 라우팅을 구현하기 위해 사용하는 대표적인 라이브러리가 react-router-dom 이다. 이를 통해 페이지 이동, URL 파라미터 처리, 라우트 보호, 리다이렉트 같은 기능을 손쉽게 구현할 수 있다.

 

프로젝트에서는 로그인 상태 (accessToken) 를 기준으로 인증 여부를 판단하고 인증되지 않은 사용자는 /login 페이지로 이동되도록 한다. 모든 실제 화면은 / 루트 아래에서 <PrivateRoute> 와 <MainLayout> 을 통해 보여지고 Zustand 를 이용한 토큰 상태 관리를 통해서 이를 제어한다. 라우팅 구조는 React Router v6 + Navigate + Outlet 기반이다.

 

다음은 프로젝트에 적용된 라우팅 구조를 설명한다.


1️⃣ App.tsx

import AppRoutes from '@/routes/AppRoutes';
import { BrowserRouter } from 'react-router-dom';

function App() {
  return (
    <BrowserRouter>
      <AppRoutes />
    </BrowserRouter>
  );
}

export default App;

앱 전체에 React Router 를 적용하는 진입점이다. <BrowserRouter> 는 HTML5 history API 를 사용하는 라우터 컨테이너이다. 실제 라우팅 구조는 AppRoutes 에서 설정한다.

react-router-dom & BrowserRouter

react-router-dom

React 전용 라우팅 라이브러리로 브라우저 환경에 맞는 라우터 (BrowserRouter) 와 모바일 / 네이티브 환경에 맞는 라우터 (NavigateRouter, HashRouter) 를 구분하여 제공해준다.

  • <Routes>, <Route> : 경로 기반 컴포넌트 매핑
  • <Link> : 페이지 전환용 앵커
  • <Navigate> : 조건에 따라 강제 이동
  • useLocation, useNavigate, useParams : 훅 기반 라우팅 컨트롤

BrowserRouter

항목 설명
정의 react-router-dom에서 제공하는 브라우저 전용 라우터
동작 방식 브라우저의 History API (pushState, replaceState)를 사용하여 URL을 변경
장점 실제 주소창 URL을 깔끔하게 유지 (예: /login, /users/1)
특징 페이지를 새로고침하지 않고도 URL 변경 및 이동 가능

 

모든 라우팅 구조는 <BrowserRouter> 하위에서 <Routes> 와 <Route> 로 정의된다.


2️⃣ AppRoutes.tsx

import MainLayout from '@/layout/MainLayout';
import LoginPage from '@/pages/auth/LoginPage';
import DashboardPage from '@/pages/DashboardPage';
import NotFoundPage from '@/pages/errors/NotFoundPage';

import { PrivateRoute } from '@/components/route/PrivateRoute';
import RoleGuard from '@/components/route/RoleGuard';
import UnauthorizedPage from '@/pages/errors/UnauthorizedPage';
import { useAuthStore } from '@/store/useAuthStore';
import { Navigate, Route, Routes } from 'react-router-dom';

const AppRoutes = () => {
  const { accessToken } = useAuthStore();

  return (
    <Routes>
      {/* 1. 로그인 페이지 */}
      <Route
        path="/login"
        element={accessToken ? <Navigate to="/dashboard" replace /> : <LoginPage />}
      />

      {/* 2. 보호된 페이지 */}
      <Route
        path="/"
        element={
          <PrivateRoute>
            <MainLayout />
          </PrivateRoute>
        }
      >
        {/* 대시보드 - 모든 사용자 */}
        <Route path="dashboard" element={<DashboardPage />} />

        {/* 공지사항 */}
        <Route path="notices" element={<NoticeList />} />
        <Route
          path="notices/write"
          element={
            <RoleGuard requiredAuth="MANAGER">
              <NoticeWrite />
            </RoleGuard>
          }
        />

        {/* 보고서 - 모든 사용자 */}
        <Route path="reports" element={<ReportList />} />
        <Route path="reports/write" element={<ReportWrite />} />

        {/* 사용자 */}
        <Route path="users" element={<UserList />} />
        <Route
          path="users/write"
          element={
            <RoleGuard requiredAuth="MANAGER">
              <UserWrite />
            </RoleGuard>
          }
        />
        <Route path="users/password" element={<UserPassword />} />

        {/* 휴가 - 모든 사용자 */}
        <Route path="vacations" element={<VacationList />} />
        <Route path="vacations/write" element={<VacationWrite />} />
        <Route path="vacations/approval" element={<VacationApproval />} />

        {/* 휴가 정보 - 관리자만 */}
        <Route
          path="balances"
          element={
            <RoleGuard requiredAuth="ADMIN">
              <VacationBalanceList />
            </RoleGuard>
          }
        />
        <Route
          path="balances/write"
          element={
            <RoleGuard requiredAuth="ADMIN">
              <VacationBalanceWrite />
            </RoleGuard>
          }
        />

        {/* 권한 없음 페이지 */}
        <Route path="/unauthorized" element={<UnauthorizedPage />} />

        {/* 기본 경로 리다이렉트 */}
        <Route index element={<Navigate to="dashboard" replace />} />

        {/* 3. 로그인 후 잘못된 페이지 이동 시 : 404 에러 페이지 */}
        <Route path="*" element={<NotFoundPage />} />
      </Route>

      {/* 4. 로그인 하지 않았으면 로그인 페이지로 이동 */}
      <Route path="*" element={<Navigate to="/login" replace />} />
    </Routes>
  );
};

export default AppRoutes;

<Routes> 태그 안에 <Route> 를 통해서 렌더링 될 컴포넌트와 URL 주소를 지정한다. accessToken 의 상태를 통해서 로그인한 사용자의 라우팅 주소를 설정하고 <PrivateRoute> 를 통해서 인증되지 않은 사용자에 대해서 로그인 페이지로 이동하도록 한다.

 

<RoleGuard /> 를 통해서 로그인 한 사용자의 권한에 따라 접근 제어를 한다. 권한에 맞지 않는 요청을 했을 때는 권한 없음 페이지를 띄우게 되는데 이는 Outlet 의 범위 안에 있기 때문에 MainLayout 의 Content 영역에 해당 페이지가 띄워진다. (Topbar, Sidebar 유지)

 

라우팅과 MainLayout 의 Outlet 구조 적용

import SideBar from '@/components/common/Sidebar';
import TopBar from '@/components/common/Topbar';
import { Outlet } from 'react-router-dom';

const MainLayout = () => {
  return (
    <div className="flex h-screen bg-gray-100">
      {/* 사이드바 */}
      <SideBar />

      {/* 메인 컨텐츠 영역 */}
      <div className="flex flex-col flex-1">
        {/* 상단 바 */}
        <TopBar />
        {/* 메인 컨텐츠 영역 */}
        <main className="flex-1 overflow-auto p-6">
          <Outlet /> {/* 중첩 라우트 표시 위치 */}
        </main>
      </div>
    </div>
  );
};

export default MainLayout;

Outlet 은 react-router-dom 에서 제공하는 중첩 라우팅 을 위한 컴포넌트이다. 부모 라우트 안에서 자식 라우트가 렌더링 될 위치를 지정하는 용도로 사용된다. 보통 공통 레이아웃 (MainLayout) 컴포넌트에 내부에 작성되며, Header 와 Sider 는 고정된 채로 자식페이지가 <Outlet /> 의 영역에서 동적으로 바뀌어 렌더링된다.

목적 설명
공통 레이아웃 구성 Header, Sidebar 같은 공통 UI는 고정하고 그 안에서 자식 화면만 바뀌게 함
중첩 구조 대응 여러 depth의 경로 구조 (/users, /users/:id, /users/edit) 대응 가능
컴포넌트 분리 UI 레이아웃과 실제 페이지 화면을 분리해서 유지보수 용이

3️⃣ PrivateRoute.tsx

import { useAuthStore } from '@/store/useAuthStore';
import { Navigate, useLocation } from 'react-router-dom';

// props 로 children 값이 들어옴 : 타입 정의
interface PrivateRouteProps {
  children: React.ReactNode; // <PrivateRoute> 태그 안의 모든 JSX 코드를 받게 한다.
}

const PrivateRoute = ({ children }: PrivateRouteProps) => {
  const { accessToken } = useAuthStore();
  const location = useLocation();

  if (!accessToken) {
    return <Navigate to="/login" state={{ from: location }} replace />;
  }

  return <>{children}</>;
};

export default PrivateRoute;

PrivateRoute 는 accessToken 의 상태에 따라서 로그인 페이지로 이동할 것인지 컴포넌트의 요소를 그릴지 결정한다. 이를 통해서 로그인되지 않은 사용자에 대한 처리를 한다.

  • React.ReactNode : JSX 에서 쓸 수 있는 모든 타입을 포함하는 타입 (JSX 요소, 문자열, null, 배열 등.. 이는 PirvateRoute 로 감쌀 수 있게 한다)
  • const location = useLocation(); : 리액트 라우터에서 현재 URL의 경로, 쿼리, 해시 등의 정보를 가져오는 훅이다. 이는 사용자가 어디로 가려는지 알 수 있고 원래 가려던 페이지로 돌아가기 위해 쓴다.

 

useLocation() 사용 예시 : 로그인 성공 후 다시 원래 페이지로 이동

// 코드 위치 : LoginPage.tsx
const location = useLocation();
const from = location.state?.from?.pathname || '/'; // 가려던 페이지 or /
navigate(from);
  • 토큰이 만료되어 원래 이동하려던 “공지사항 페이지” 로 이동되지 못하고 로그인 페이지로 넘어온다.
  • 로그인이 성공하면 원래 이동하려면 “공지사항 페이지” 로 이동할 수 있도록 navigate 한다.

4️⃣ RoleGuard.tsx

import { useAuthStore } from '@/store/useAuthStore';
import type { UserInfo } from '@/types/auth';
import React from 'react';
import { Navigate } from 'react-router-dom';

// 사용자 권한 타입 (기존 UserInfo.userAuth와 동일)
export type UserAuth = UserInfo['userAuth'];

// 권한 레벨 매핑
const AUTH_LEVELS: Record<UserAuth, number> = {
  USER: 1,
  MANAGER: 2,
  ADMIN: 3,
};

interface RoleGuardProps {
  children: React.ReactNode;
  requiredAuth: UserAuth;
  fallbackPath?: string;
}

const RoleGuard: React.FC<RoleGuardProps> = ({
  children,
  requiredAuth,
  fallbackPath = '/unauthorized',
}) => {
  const { userInfo } = useAuthStore();

  // 권한 체크
  const currentUserAuth = userInfo?.userAuth;
  const hasPermission =
    currentUserAuth && AUTH_LEVELS[currentUserAuth] >= AUTH_LEVELS[requiredAuth];

  if (!hasPermission) {
    return <Navigate to={fallbackPath} replace />;
  }

  return <>{children}</>;
};

export default RoleGuard;

RoleGuard 는 사용자 권한에 따라 페이지 접근을 제어하는 라우팅 보호 컴포넌트이다. 로그인된 사용자의 권한이 특정 권한 등급 이상인지 판단하며, 조건을 만족하지 않으면 지정된 경로로 리디렉션 시킨다.

 

로그인된 사용자의 권한은 로그인 시 지정되는 UserInfo 를 userAuthStore 에서 zustand 가 관리하고 있기 때문에 로그인 후에 페이지 이동 시 UserInfo 의 userAuth 의 값에 따라 판단된다.


'Project > Frontend' 카테고리의 다른 글

[Groupware] React : axios (API 통신)  (0) 2025.06.04
'Project/Frontend' 카테고리의 다른 글
  • [Groupware] React : axios (API 통신)
arraysort
arraysort
arraysort 님의 블로그 입니다.
  • arraysort
    arraysort 님의 블로그
    arraysort
  • 전체
    오늘
    어제
    • 분류 전체보기 (14)
      • Study (5)
        • Java (3)
        • DataBase (1)
        • Spring-Boot (1)
        • React (0)
        • WEB (0)
      • Project (9)
        • Backend (5)
        • Frontend (2)
        • Database (2)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

  • 공지사항

  • 인기 글

  • 태그

    SQL Mapper
    react
    FilterChain
    API
    objects.eqauls()
    spring boot
    CDB
    TypeScript
    VO
    lombok
    Spring Security
    mabatis
    Database
    backend
    DTO
    oracle
    utilityclass
    SQL
    Groupware
    java
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.3
arraysort
[Groupware] React : Route (라우팅 설정)
상단으로

티스토리툴바