공모전 준비를 하면서 오랜만에 프론트 작업을 하게 됐다. 평소에도 프론트 작업할 때는 공통적인 레이아웃 구조나 스타일을 먼저 정해두는 게 중요하다고 생각하는 편이다. 그래서 이번에도 초기 세팅을 각자 맡아서 하기로 했는데... 프론트 세팅을 맡은 팀원이 작업을 잘 안 해서 결국 내가 화면 개발을 하면서 레이아웃 구조까지 손대게 되었다.
그 과정에서 라우팅 구조에 대해 고민한 내용과 중첩 라우팅(Outlet) 방식으로 구조를 바꾸게 된 이유를 정리해보려 한다.
팀원이 처음 만들어둔 라우터 구조
우선 팀원이 만든 초기 라우터 구조는 아래와 같았다. 최상위 App.js에서 각각의 기능 영역(User, Vendor, Admin)을 별도의 라우트로 분리한 구조였다.
// App.js
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';
import Main from './pages/Main.jsx';
import UserRoutes from './routes/UserRoutes';
import VendorRoutes from './routes/VendorRoutes';
import AdminRoutes from './routes/AdminRoutes';
import "./assets/css/all.css";
function App() {
return (
<Router>
<Routes>
<Route path='/' element={<Main />} />
<Route path="/user/" element={<UserRoutes />} />
<Route path="/vendor/" element={<VendorRoutes />} />
<Route path="/admin/" element={<AdminRoutes />} />
</Routes>
</Router>
);
}
export default App;
예시를 하나 들자면, Vendor 영역의 하위 라우트는 이렇게 구성되어 있었다.
// VendorRoutes.jsx
import { Routes, Route } from 'react-router-dom';
import VendorSidebar from "../pages/vendor/VendorSidebar";
import ApplicantManagement from "../pages/vendor/ApplicantManagement";
import InfoManagement from "../pages/vendor/InfoManagement";
import NotificationSettings from "../pages/vendor/NotificationSettings";
const VendorRoutes = () => {
return (
<Routes>
<Route path="/sidebar" element={<VendorSidebar />} />
<Route path="/applicant/management" element={<ApplicantManagement />} />
<Route path="/info/management" element={<InfoManagement />} />
<Route path="/notification/settings" element={<NotificationSettings />} />
</Routes>
);
};
export default VendorRoutes;
라우트 구조 자체는 괜찮았지만, 공통 레이아웃(헤더/푸터 포함)을 어떻게 적용할지에 대한 고민이 부족한 상태였다.
내가 생각한 레이아웃 구조: Layout 컴포넌트로 감싸는 방식
그래서 내가 생각한 방식은 App.js의 라우터를 Layout.jsx로 감싸고, 그 안에 공통된 헤더와 푸터를 포함시키는 구조였다. 그리고 메인 영역에는 각 페이지 컴포넌트가 children으로 들어가는 형태다.
// Layout.jsx
import React from 'react';
import Header from './components/Header';
import Footer from './components/Footer';
const Layout = ({ children }) => {
return (
<div>
<Header />
<main>{children}</main>
<Footer />
</div>
);
};
export default Layout;
// App.js (초기 내가 만든 구조)
function App() {
return (
<Router>
<Layout>
<Routes>
<Route path='/' element={<Main />} />
<Route path="/user/*" element={<UserRoutes />} />
<Route path="/vendor/*" element={<VendorRoutes />} />
<Route path="/admin/*" element={<AdminRoutes />} />
</Routes>
</Layout>
</Router>
);
}
이 방식은 단순하고 직관적인 장점이 있지만, 라우트가 많아질수록 모든 페이지를 Layout으로 감싸야 해서 반복 코드가 생기고, 중첩 구조를 표현하는 데 어려움이 있었다.
그래서 고민하던 중, Outlet이라는 개념을 알게 되었다.
Outlet이란?
React Router v6에서 도입된 Outlet은 중첩된 라우트를 렌더링하는 위치를 지정하는 컴포넌트이다. 부모 라우트가 공통된 레이아웃을 가지고 있고, 그 안에 들어가는 자식 라우트를 구성할 때 유용하다.
예를 들어 Layout이 헤더/푸터를 포함한 공통 UI라면, 각 페이지는 <Outlet />이 위치한 곳에 렌더링된다. 덕분에 코드 구조가 더 선언적이고 직관적이 되며, 라우트 구조와 UI 계층이 일치하게 된다.
Outlet을 이용한 중첩 라우트 구성
React Router에서 제공하는 Outlet이라는 컴포넌트를 사용하면 중첩 라우트를 보다 깔끔하게 구현할 수 있다. Outlet을 사용하면 레이아웃 내부에서 하위 라우트의 컴포넌트를 렌더링할 위치를 지정할 수 있다.
// Layout.jsx
import React from 'react';
import { Outlet } from 'react-router-dom';
import Header from './components/Header';
import Footer from './components/Footer';
const Layout = () => {
return (
<div>
<Header />
<main>
<Outlet /> {/* 하위 라우트 컴포넌트가 렌더링될 자리 */}
</main>
<Footer />
</div>
);
};
export default Layout;
// App.js (Outlet 방식으로 변경)
function App() {
return (
<Router>
<Routes>
<Route element={<Layout />}>
<Route path='/' element={<Main />} />
<Route path="/user/*" element={<UserRoutes />} />
<Route path="/vendor/*" element={<VendorRoutes />} />
<Route path="/admin/*" element={<AdminRoutes />} />
</Route>
</Routes>
</Router>
);
}
방식 비교
구분 | children props 방식 | Outlet 방식 |
구조 | 라우트를 Layout으로 감쌈 | 레이아웃 안에 Outlet으로 중첩 렌더링 |
장점 | 단순한 구조, 빠른 구현 | 선언적이고 확장성 좋음 |
단점 | 라우트 많아지면 반복 코드 발생 | 개념 익숙하지 않으면 처음에 헷갈릴 수 있음 |
확장성 | 중첩 레이아웃 어려움 | 중첩 레이아웃 용이 (예: 관리자 전용 사이드바 등) |
마무리
프론트는 진짜 초기 구조를 어떻게 잡느냐가 나중 유지보수나 확장성에 큰 영향을 미친다는 걸 다시 한번 느꼈다. 처음에는 그냥 Layout으로 감싸는 방식이 익숙했는데, Outlet 방식으로 전환하고 나서 확실히 구조가 깔끔해지고 유지보수가 쉬워졌다.
앞으로는 프로젝트 시작할 때부터 중첩 라우트 구조를 염두에 두고 설계해야겠다는 생각이 들었다. React Router의 최신 흐름도 그렇고, 팀원들과 협업할 때도 더 명확한 구조를 공유할 수 있어서 훨씬 좋다.
'React' 카테고리의 다른 글
[React] 사이드 내비게이션 컴포넌트 구현 (0) | 2025.04.22 |
---|---|
[React] react-icons v5.5.0 오류 해결: lucide-react로 전환 (0) | 2025.02.26 |
[React] React에서 Drag & Drop 구현 – react-beautiful-dnd 사용 (0) | 2025.02.08 |
[React] fixed로 고정된 위치, 알고 보니 뷰포트 기준 (0) | 2025.01.22 |
[React] REST, REST API, RESTful API의 개념과 특징, 장단점, 사용 방법 및 예시 (0) | 2025.01.21 |