
이 문서는 React Router를 효율적으로 사용하기 위한 방법을 설명합니다. 복잡해지는 프로젝트의 경로 관리, 라우터 설정 모듈화, 그리고 Not Found 페이지 처리 방법을 학습합니다.
path="*"를 이용한 Not Found(404) 페이지 처리경로 문자열을 직접 입력하는 대신, 별도의 파일에 상수로 정의하고 이를 불러와 사용하면 다음과 같은 장점이 있습니다.
"/posts/:postId" 같은 문자열보다 PATHS.POST_DETAIL처럼 의미 있는 이름으로 경로를 사용할 수 있습니다.경로 상수를 관리할 constants 폴더와 paths.js 파일을 생성합니다.
📁 src/
├── 📁 pages/
│ ├── 📁 DummyPages/
│ │ ├── ⚛️ DummyHome.jsx
│ │ ├── ⚛️ Posts.jsx
│ │ ├── ⚛️ PostDetail.jsx
│ │ └── ⚛️ Products.jsx
│ ├── 📁 RootPages/
│ │ ├── ⚛️ RootHome.jsx
│ │ └── ⚛️ About.jsx
├── 📁 layouts/
│ ├── ⚛️ RootLayout.jsx
│ └── ⚛️ DummyLayout.jsx
├── 📁 constants/
│ └── 🚦 paths.js # 경로 상수 관리 파일
└── 📁 router/
└── 🚦 index.js
src/constants/paths.js
// 경로 상수 객체 PATHS
const PATHS = {
// RootLayout을 사용하는 경로
ROOT: {
INDEX: "/",
ABOUT: "/about",
},
// DummyLayout을 사용하는 경로
DUMMY: {
INDEX: "/dummy",
POSTS: "/dummy/posts",
PRODUCTS: "/dummy/products",
// 동적 경로(Dynamic Route)
// 1. 라우터 정의에 사용될 패턴
POST_DETAIL: "/dummy/posts/:postId",
// 2. Link 또는 navigate()에서 사용할 함수
getPostDetailPath: (postId) => `/dummy/posts/${postId}`,
},
};
export default PATHS;
💡 동적 경로 관리
동적 경로는 두 가지 형태로 관리하는 것이 편리합니다. 하나는 라우터 설정을 위한 문자열 패턴(:postId)이고, 다른 하나는 실제 파라미터를 넣어 완전한 URL을 생성하는 함수(getPostDetailPath)입니다.
src/router/index.js
import { createBrowserRouter } from "react-router-dom";
import RootLayout from "../layouts/RootLayout";
import DummyLayout from "../layouts/DummyLayout";
import RootHome from "../pages/RootPages/RootHome";
import About from "../pages/RootPages/About";
import DummyHome from "../pages/DummyPages/DummyHome";
import Posts from "../pages/DummyPages/Posts";
import PostDetail from "../pages/DummyPages/PostDetail";
import Products from "../pages/DummyPages/Products";
// 경로 상수 객체 PATHS 불러오기
import PATHS from "../constants/paths";
const router = createBrowserRouter([
{
path: PATHS.ROOT.INDEX,
Component: RootLayout,
children: [
{ index: true, Component: RootHome },
{ path: PATHS.ROOT.ABOUT, Component: About },
],
},
{
path: PATHS.DUMMY.INDEX,
Component: DummyLayout,
children: [
{ index: true, Component: DummyHome },
{ path: PATHS.DUMMY.POSTS, Component: Posts },
{ path: PATHS.DUMMY.POST_DETAIL, Component: PostDetail },
{ path: PATHS.DUMMY.PRODUCTS, Component: Products },
],
},
]);
export default router;
<Link> 컴포넌트나 navigate 함수에서 정의된 경로 상수를 사용합니다.
import { useNavigate, Link } from "react-router-dom";
import PATHS from "../constants/paths";
export default function Component() {
const navigate = useNavigate();
return (
<>
{/* 정적 경로 사용 */}
<button onClick={() => navigate(PATHS.ROOT.INDEX)}>홈 페이지</button>
<Link to={PATHS.ROOT.ABOUT}>소개</Link>
<Link to={PATHS.DUMMY.POSTS}>Posts</Link>
{/* 동적 경로 사용 */}
<Link to={PATHS.DUMMY.getPostDetailPath(1)}>Post #1</Link>
<Link to={PATHS.DUMMY.getPostDetailPath(2)}>Post #2</Link>
</>
);
}
프로젝트 규모가 커지면 router/index.js 파일이 비대해질 수 있습니다. 이때 레이아웃이나 기능 단위로 라우트 설정을 분리하면 관리가 용이해집니다.
라우트 설정 파일을 담을 routes 폴더를 생성하고, 모듈화할 파일을 만듭니다.
📁 src/
└── 📁 router/
├── 🚦 index.js # 메인 라우터 파일
└── 📁 routes/
├── 🚦 rootRoutes.js # RootLayout 관련 라우트
└── 🚦 dummyRoutes.js # DummyLayout 관련 라우트
각 파일에 라우트 배열을 정의하고 export 합니다.
src/router/routes/rootRoutes.js
import RootLayout from "../../layouts/RootLayout";
import RootHome from "../../pages/RootPages/RootHome";
import About from "../../pages/RootPages/About";
import PATHS from "../../constants/paths";
const rootRoutes = [
{
path: PATHS.ROOT.INDEX,
Component: RootLayout,
children: [
{ index: true, Component: RootHome },
{ path: PATHS.ROOT.ABOUT, Component: About },
],
},
];
export default rootRoutes;
src/router/routes/dummyRoutes.js
import DummyLayout from "../../layouts/DummyLayout";
import DummyHome from "../../pages/DummyPages/DummyHome";
import Posts from "../../pages/DummyPages/Posts";
import PostDetail from "../../pages/DummyPages/PostDetail";
import Products from "../../pages/DummyPages/Products";
import PATHS from "../../constants/paths";
const dummyRoutes = [
{
path: PATHS.DUMMY.INDEX,
Component: DummyLayout,
children: [
{ index: true, Component: DummyHome },
{ path: PATHS.DUMMY.POSTS, Component: Posts },
{ path: PATHS.DUMMY.POST_DETAIL, Component: PostDetail },
{ path: PATHS.DUMMY.PRODUCTS, Component: Products },
],
},
];
export default dummyRoutes;
router/index.js에서 분리된 라우트 설정들을 불러와 전개 연산자(...)를 사용해 하나의 배열로 합칩니다.
src/router/index.js
import { createBrowserRouter } from "react-router-dom";
import rootRoutes from "./routes/rootRoutes";
import dummyRoutes from "./routes/dummyRoutes";
// 분리된 라우트 설정을 합쳐서 라우터 생성
const router = createBrowserRouter([...rootRoutes, ...dummyRoutes]);
export default router;
사용자가 존재하지 않는 주소로 접근했을 때, "페이지를 찾을 수 없습니다"와 같은 안내를 보여줄 수 있습니다.
src/pages/NotFound.jsx
import { Link } from 'react-router-dom';
export default function NotFound() {
return (
<div>
<h1>404 - Page Not Found</h1>
<p>요청하신 페이지를 찾을 수 없습니다.</p>
<Link to="/">홈으로 돌아가기</Link>
</div>
);
}
path="*"는 와일드카드 역할을 하여 위에 정의된 어떤 경로와도 일치하지 않을 경우 렌더링될 컴포넌트를 지정합니다. 이 설정은 반드시 라우터 배열의 가장 마지막에 위치해야 합니다.
src/router/index.js
import { createBrowserRouter } from "react-router-dom";
import rootRoutes from "./routes/rootRoutes";
import dummyRoutes from "./routes/dummyRoutes";
import NotFound from "../pages/NotFound"; // Not Found 컴포넌트 불러오기
const router = createBrowserRouter([
...rootRoutes,
...dummyRoutes,
{
// 위에 정의된 경로 외 모든 경로에 대해 처리
path: "*",
Component: NotFound,
},
]);
export default router;
createBrowserRouter에 전달할 배열을 기능별/레이아웃별 파일로 분리하고, 메인 파일에서 합쳐서 사용하면 관리가 용이합니다.path="*" 설정을 라우터 배열의 맨 마지막에 추가하여 존재하지 않는 경로에 대한 처리를 구현합니다.