Notice
Recent Posts
Recent Comments
Link
«   2026/07   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
Tags
more
Archives
Today
Total
관리 메뉴

mimizae 님의 블로그

React Route 초기 세팅 본문

FE

React Route 초기 세팅

mimizae 2026. 2. 2. 22:44

37기 DIVE SOPT에서 진행한 프로젝트 CareNA 프로젝트의 초기세팅 과정을 담은 글입니다! 🙌🏻


⚙️ react-router 설치

 왜 react-router-dom이 아니라 react-router?

 

React Router는 v7부터 패키지 구조가 변경되었다.

기존에는 웹 환경에서 react-router-dom을 설치하는 것이 일반적이었지만, v7부터는 패키지가 단일화되어 새 프로젝트 기준으로 react-router만 사용하는 것이 권장된다.

실제로 package.json을 확인해 보면, 메인 버전이 7 이상으로 시작하는 경우가 대부분이다.

실제 설치한 버전

⚠️ 버전별로 헷갈리기 쉬운 포인트

React Router는 v5 → v6으로 넘어오면서 라우팅 방식, 훅, API 구조가 크게 변경되었다.

따라서 라우터 관련 검색 시 v5 이하 문서나 예제는 현재(v6/v7)에서 동작하지 않을 수 있으니 주의가 필요하다…!!!!!!!!!

다행히 v6과 v7은 구조와 사용 방식이 매우 유사해 v6 기반 예제들도 대부분 참고 가능하다. 😮‍💨

 

⚠️ 데이터 라우터 도입 (v6.4+)

React Router v6.4부터 데이터 로딩과 에러 핸들링이 내장된 데이터 라우터 방식이 도입되었다.


🔻 createBrowserRouter + RouterProvider

더보기

🧵 라우터를 구성하는데 가장 자주 쓰이는 2가지 모듈.

  • createBrowserRouter
    • createBrowserRouter는 브라우저 환경에서 사용할 수 있는 라우터를 생성.
    • 브라우저의 히스토리 API를 사용하여 페이지 이동을 관리.
    • 라우트 경로와 그에 대응하는 컴포넌트를 설정하는 데 사용.
  • RouterProvider
    • React 앱에 라우터 인스턴스를 공급.
    • RouterProvider에 라우터를 전달하면, 해당 라우터에 정의된 경로와 컴포넌트 설정을 앱 전체에서 사용할 수 있음. 

 

이 방식은 현재 공식적으로 권장되는 라우팅 방식이다.

다만,

  • BrowserRouter
  • Routes
  • Route

로 구성하는 기존의 선언적 라우팅 방식도 여전히 공식 지원되며, v7 문서에서도 별도의 시작 가이드를 제공하고 있다!!!!!!

따라서 프로젝트의 복잡도와 요구사항에 따라 적절한 방식을 선택하면 된다.

 

⚠️ v7에서 react-router-dom은?

v7부터는 react-router-dom이 호환용 패키지가 되었다.

이는 v6에서 v7로 마이그레이션하는 프로젝트를 위한 용도이며, 새 프로젝트 기준으로는 react-router만 사용하는 것이 권장된다.

 

신규 프로젝트 → react-router
기존 v6 프로젝트 → 필요 시 react-router-dom 유지

 

라는 기준으로 이해하면 된다!


⚙️ 라우터 구조의 분리

React Router를 사용할 때 컴포넌트 내부에서 Route를 직접 나열하는 방식은 소규모 프로젝트에서는 크게 문제가 되지 않는다.

하지만 프로젝트 규모가 커지고 페이지 수와 라우팅 조건이 늘어나기 시작하면, 라우터 코드가 점점 관리하기 어려운 영역이 되기 쉽다!

 

특히 다음과 같은 문제가 자주 발생한다.

  • 라우트 수가 많아질수록 한 파일에 코드가 과도하게 몰려 가독성이 급격히 떨어진다.
  • 새로운 페이지를 추가할 때마다 라우트 정의, 경로 문자열, 레이아웃 구조 등을 여러 곳에서 함께 수정해야 한다.
  • 라우팅 구조가 분산되어 있으면 다른 사람이 만든 흐름을 파악하는 데 시간이 오래 걸리고, 의도치 않게 중복되거나 잘못된 경로를 추가할 가능성도 커진다.

이러한 문제를 줄이기 위해 본 프로젝트에서는 라우터 관련 역할을 명확히 분리한 구조를 선택했다.

실제 파일 구조

 

paths.ts

export const routePath = {
  LAYOUT: '/',
  HOME: '/home',
  MY_PAGE: '/my-page',
  // 페이지 작업 시 추가
} as const;

export type Routes = (typeof routePath)[keyof typeof routePath];

 

paths.ts는 애플리케이션에서 사용되는 모든 라우트 경로를 한 곳에서 관리하기 위한 파일이다.

 

라우트 경로를 문자열로 직접 작성하는 대신 상수로 정의함으로써,

  • 경로 오타를 방지할 수 있고
  • 경로 변경 시 한 파일만 수정하면 되며
  • 라우팅 구조를 한눈에 파악할 수 있다!!

또한 as const와 타입 추출을 통해 라우트 경로를 타입 레벨에서도 안전하게 관리할 수 있도록 했다.

 

router.tsx

import { createBrowserRouter, Navigate } from "react-router";
import { Layout } from "./layout";
import { routePath } from "./paths";

export const router = createBrowserRouter([
	{
		path: routePath.LAYOUT,
		element: <Layout />,
		children: [
			{
				index: true,
				element: <Navigate to={routePath.HOME} replace />,
			},
			{
				path: routePath.HOME,
				element: <></>,
			},
			{
				path: routePath.MY_PAGE,
				element: <></>,
			},
		],
	},
]);

 

router.tsx는 애플리케이션 전체의 라우팅 구조를 정의하는 중심 파일이다.

  • 어떤 경로가 어떤 컴포넌트를 렌더링하는지
  • 어떤 페이지가 어떤 레이아웃 아래에 위치하는지
  • 기본 진입 경로(/)에서 어디로 이동할지

와 같은 라우팅 흐름을 한눈에 확인할 수 있다.

 

특히 Layout을 기준으로 하위 라우트를 구성함으로써, 공통 UI를 사용하는 페이지들을 자연스럽게 그룹화할 수 있다!!!

 

layout.tsx

import { Outlet } from "react-router";

export const Layout = () => {
	return (
		<div>
			{/* Header 위치 */}
			<Outlet />
		</div>
	);
};

 

layout.tsx 는 여러 페이지에서 공통으로 사용되는 UI 구조를 담당하는 레이아웃 컴포넌트이다.

Outlet은 현재 라우트에 매칭된 하위 페이지 컴포넌트가 렌더링되는 위치를 의미하며, 이를 통해 레이아웃과 페이지를 명확하게 분리할 수 있다.

 

현재 디자인을 참고하면 모든 페이지에 헤더가 존재하지만, 디자이너 팀원 분과 소통 해 보니 로그인 페이지에는 헤더(로고)가 없어질 가능성이 있다고 한다. 이렇게 어떤 페이지에는 헤더가 없고, 다른 페이지에는 헤더가 있는 이런 상황은 어떻게 처리해야 할까? 🤔

 

1️⃣ 레이아웃을 분리하는 방법

 

DefaultLayout.tsx → 헤더 있는 페이지

import { Outlet } from "react-router";
import Header from "@/components/Header";

export const DefaultLayout = () => {
  return (
    <>
      <Header />
      <Outlet />
    </>
  );
};
 
BlankLayout.tsx  → 헤더 없는 페이지
import { Outlet } from "react-router";

export const BlankLayout = () => <Outlet />;
 
이렇게 layout을 나눈다면 router.tsx에서 나눈 layout들을 아래처럼 적용할 수 있을 것이다.
import { createBrowserRouter, Navigate } from "react-router";
import { Layout } from "./layout";
import { routePath } from "./paths";

export const router = createBrowserRouter([
  // 헤더 있는 일반 페이지
  {
    element: <DefaultLayout />,
    children: [
      { index: true, element: <Navigate to={routePath.HOME} replace /> },
      { path: routePath.HOME, element: <Home /> },
      { path: routePath.MY_PAGE, element: <MyPage /> },
      // 다른 페이지도 여기 추가!!
    ],
  },

  // 헤더 없는 페이지 (로그인 등)
  {
    element: <BlankLayout />,
    children: [
      { path: routePath.LOGIN, element: <Login /> },
      // 헤더 없는 페이지가 생기면 이곳에!
    ],
  },
]);

 

 

2️⃣ Layout 안에서 조건부 렌더링

import { Outlet, useLocation } from "react-router";
import Header from "@/components/Header";

export const Layout = () => {
  const location = useLocation();
  const hideHeader = location.pathname === "/login"; // 로그인 페이지만 헤더 숨김

  return (
    <div>
      {!hideHeader && <Header />}
      <Outlet />
    </div>
  );
};

 

 

이외에도, 다양한 방식이 있겠지만! 우리 프로젝트에서 선택한 방식은 React Route의 handle을 이용하는 방식이다.

자세한 글은 이곳을 참고해 주길 바란다!


💡 인증 라우트? ProtectedRoute?

우리 서비스는 건강 검진 기록이라는 민감한 정보를 다루는 서비스이기 때문에, 인증되지 않은 사용자가 내부 페이지에 접근하는 것은 반드시 제한할 필요가 있다!!

이때 페이지 컴포넌트 내부에서 매번 인증 여부를 확인하는 방식보다는, 라우터 레벨에서 접근 자체를 제어하는 방식이 구조적으로 더 깔끔하고 안전하다고 판단했다.

 

이러한 요구사항을 해결하기 위한 패턴이 PrivateRoute 또는 ProtectedRoute 이다.

ProtectedRoute는 사용자의 인증 상태를 확인한 뒤, 조건을 만족하지 못할 경우 특정 페이지로 리다이렉트시키고 조건을 만족하는 경우에만 하위 라우트를 렌더링하는 역할을 한다!!!

 

이를 통해

  • 인증 로직을 한 곳에서 관리할 수 있고
  • 각 페이지 컴포넌트는 인증 여부를 신경 쓰지 않아도 되며
  • “어떤 페이지가 보호 대상인지”를 라우터 구조만 보고도 파악할 수 있다.
ProtectedRoute.tsx 예시
const ProtectedRoute = () => {
    const { isLoggedIn } = useAuth(); // 사용자가 인증되었는지 확인할 수 있는 훅 예시

    if (!isLoggedIn) {
        // 유저 정보가 없다면 홈으로, 혹은 로그인 페이지로 가게 할 수 있음
        return <Navigate to="/" replace={true} />;
    }

    // 유저 정보가 있다면 자식 컴포넌트를 보여줌
    return <Outlet />;
};

export default ProtectedRoute;

 

그렇다면 router.tsx는 아래와 같이 수정될 것이다!!

import { createBrowserRouter, Navigate } from "react-router";
import { Layout } from "./layout";
import { ProtectedRoute } from "./ProtectedRoute";
import { routePath } from "./paths";

export const router = createBrowserRouter([
  // 공개 라우트 (로그인 / 회원가입 등)
  {
    path: routePath.LOGIN,
    element: <></>,
  },

  // 보호된 라우트
  {
    element: <ProtectedRoute />, // Layout은 children에서 처리
    children: [
      {
        path: routePath.LAYOUT,
        element: <Layout />,
        children: [
           {
            index: true,
            element: <Navigate to={routePath.HOME} replace />,
          },
          {
            path: routePath.MY_PAGE,
            element: <></>,
          },
        ],
      },
    ],
  },
]);

 

 

인증 라우트를 사용한다면 다음과 같은 흐름을 가질 것이다.

URL 접근
→ "/" 매칭
→ ProtectedRoute 실행
→ 인증 OK → Layout → 실제 페이지
→ 인증 NO → /login redirect!!

 


💡 ETC

작업 PR 링크

'FE' 카테고리의 다른 글

ticker 구현기  (0) 2026.02.09
RouteHandle은 뭐고, 왜 라우트 단에서 헤더를 관리할 수 있을까?  (0) 2026.02.03
Tanstack Query 초기 세팅  (0) 2026.01.29
swagger-typescript-api 적용기  (0) 2026.01.28
pnpm이란?  (0) 2026.01.02