중첩 라우팅 소개
실제 애플리케이션에서 매우 자주 사용되는 강력한 라우팅 기법인 중첩 라우팅(Nested Routing) 에 대해 알아보겠습니다.
중첩 라우팅은 말 그대로 라우트 안에 또 다른 라우트를 정의하는 방식입니다. 이는 복잡한 UI 레이아웃을 효율적으로 관리하고, 부모 컴포넌트의 특정 영역에서만 하위 라우트에 해당하는 콘텐츠를 렌더링해야 할 때 특히 유용합니다. React Router v6에서는 Outlet
컴포넌트와 함께 이 기능을 제공합니다.
중첩 라우팅이란?
중첩 라우팅은 부모 경로에 해당하는 컴포넌트 내부에 자식 경로에 해당하는 컴포넌트를 렌더링하는 것을 의미합니다.
예시 시나리오
대시보드 페이지를 생각해 봅시다. /dashboard
경로로 접속하면 대시보드 레이아웃(사이드바, 헤더 등)이 보이고, 이 레이아웃 내의 특정 영역에 따라 다른 내용이 표시됩니다.
/dashboard/overview
→ 대시보드 레이아웃 + 개요 컴포넌트/dashboard/settings
→ 대시보드 레이아웃 + 설정 컴포넌트/dashboard/analytics
→ 대시보드 레이아웃 + 분석 컴포넌트
여기서 /dashboard
가 부모 라우트이고, /overview
, /settings
, /analytics
가 자식 라우트입니다. 자식 라우트들이 렌더링될 때 부모 라우트의 레이아웃은 그대로 유지됩니다.
Outlet
컴포넌트
React Router v6에서 중첩 라우팅을 구현하는 핵심은 Outlet
컴포넌트입니다.
- 역할:
Outlet
컴포넌트는 부모 라우트 컴포넌트 내에서 자식 라우트 컴포넌트가 렌더링될 위치를 지정합니다. - 부모 라우트가 활성화될 때,
Outlet
은 자식 라우트와 매칭되는 엘리먼트를 렌더링합니다. 만약 자식 라우트가 없다면 아무것도 렌더링하지 않습니다.
중첩 라우팅 구현 예제
대시보드와 그 하위 메뉴를 가진 간단한 애플리케이션을 만들어 중첩 라우팅을 이해해 봅시다.
대시보드 레이아웃 컴포넌트
이 컴포넌트는 모든 대시보드 하위 페이지에 공통으로 적용될 레이아웃 (DashboardLayout.js
) (예: 사이드바)을 포함하고, Outlet
을 사용하여 자식 라우트 콘텐츠를 렌더링할 위치를 지정합니다.
import React from 'react';
import { NavLink, Outlet } from 'react-router-dom'; // Outlet 임포트
function DashboardLayout() {
const navLinkStyle = ({ isActive }) => ({
display: 'block',
padding: '10px 15px',
margin: '5px 0',
backgroundColor: isActive ? '#e0e0e0' : 'transparent',
color: isActive ? '#333' : '#666',
textDecoration: 'none',
borderRadius: '5px',
transition: 'background-color 0.2s',
});
return (
<div style={{ display: 'flex', minHeight: 'calc(100vh - 100px)', border: '1px solid #ddd', borderRadius: '8px', overflow: 'hidden' }}>
{/* 사이드바 */}
<nav style={{
width: '200px',
backgroundColor: '#f4f4f4',
padding: '20px',
boxShadow: '2px 0 5px rgba(0,0,0,0.05)',
}}>
<h3 style={{ color: '#555', marginBottom: '20px' }}>대시보드 메뉴</h3>
<ul style={{ listStyle: 'none', padding: 0 }}>
<li>
{/* to="overview"는 부모 경로인 /dashboard에 상대적임 -> /dashboard/overview */}
<NavLink to="overview" style={navLinkStyle}>개요</NavLink>
</li>
<li>
<NavLink to="settings" style={navLinkStyle}>설정</NavLink>
</li>
<li>
<NavLink to="analytics" style={navLinkStyle}>분석</NavLink>
</li>
<li>
{/* 절대 경로로도 지정 가능 */}
<NavLink to="/contact" style={navLinkStyle}>문의하기 (외부)</NavLink>
</li>
</ul>
</nav>
{/* 메인 콘텐츠 영역 - 자식 라우트가 여기에 렌더링됩니다. */}
<main style={{ flexGrow: 1, padding: '20px', backgroundColor: '#fff' }}>
<h2 style={{ color: '#333', marginBottom: '20px' }}>대시보드 콘텐츠</h2>
<Outlet /> {/* <-- 여기가 중요! 자식 라우트 컴포넌트가 이 위치에 렌더링됨 */}
</main>
</div>
);
}
export default DashboardLayout;
자식 페이지 컴포넌트 생성
대시보드 하위 메뉴에 해당하는 컴포넌트들을 만듭니다. 이들은 독립적인 컴포넌트입니다.
import React from 'react';
function DashboardOverview() {
return (
<div style={{ border: '1px solid #e0e0e0', padding: '20px', borderRadius: '8px', backgroundColor: '#eaf7e7' }}>
<h3 style={{ color: '#28a745' }}>개요 페이지</h3>
<p style={{ color: '#555' }}>
여기는 대시보드의 개요를 보여주는 콘텐츠입니다. 주요 지표와 요약 정보가 표시됩니다.
</p>
</div>
);
}
export default DashboardOverview;
import React from 'react';
function DashboardSettings() {
return (
<div style={{ border: '1px solid #e0e0e0', padding: '20px', borderRadius: '8px', backgroundColor: '#e7f5ff' }}>
<h3 style={{ color: '#007bff' }}>설정 페이지</h3>
<p style={{ color: '#555' }}>
사용자 프로필, 알림 설정 등 대시보드 관련 설정을 여기서 변경할 수 있습니다.
</p>
</div>
);
}
export default DashboardSettings;
import React from 'react';
function DashboardAnalytics() {
return (
<div style={{ border: '1px solid #e0e0e0', padding: '20px', borderRadius: '8px', backgroundColor: '#fff3e0' }}>
<h3 style={{ color: '#ff9800' }}>분석 페이지</h3>
<p style={{ color: '#555' }}>
데이터 분석 및 통계 자료가 그래프와 차트 형태로 시각화되어 표시됩니다.
</p>
</div>
);
}
export default DashboardAnalytics;
App.js
에 중첩 라우트 설정
Routes
컴포넌트 내에서 부모 Route
안에 자식 Route
들을 정의합니다.
import React from 'react';
import { BrowserRouter, Routes, Route } from 'react-router-dom';
import Navbar from './components/Navbar'; // 기존 Navbar
import Home from './pages/Home';
import About from './pages/About';
import Contact from './pages/Contact';
import ProductList from './pages/ProductList';
import ProductDetail from './pages/ProductDetail';
// 대시보드 관련 컴포넌트 임포트
import DashboardLayout from './pages/DashboardLayout'; // 부모 레이아웃
import DashboardOverview from './pages/DashboardOverview'; // 자식 컴포넌트
import DashboardSettings from './pages/DashboardSettings';
import DashboardAnalytics from './pages/DashboardAnalytics';
function App() {
return (
<BrowserRouter>
<div className="App">
<Navbar />
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="/contact" element={<Contact />} />
<Route path="/products" element={<ProductList />} />
<Route path="/product/:id" element={<ProductDetail />} />
{/* 중첩 라우트 정의 */}
{/* 부모 라우트: /dashboard */}
<Route path="/dashboard" element={<DashboardLayout />}>
{/* 자식 라우트 */}
{/* index: 부모 경로(/dashboard)로 접속했을 때 기본으로 렌더링될 자식 */}
<Route index element={<DashboardOverview />} />
{/* path="overview": 최종 경로는 /dashboard/overview */}
<Route path="overview" element={<DashboardOverview />} />
{/* path="settings": 최종 경로는 /dashboard/settings */}
<Route path="settings" element={<DashboardSettings />} />
{/* path="analytics": 최종 경로는 /dashboard/analytics */}
<Route path="analytics" element={<DashboardAnalytics />} />
</Route>
{/* 일치하는 경로가 없을 때 렌더링될 컴포넌트 (404 Not Found) */}
<Route path="*" element={
<div style={{ textAlign: 'center', padding: '50px', color: '#888' }}>
<h2>404 Not Found</h2>
<p>페이지를 찾을 수 없습니다.</p>
</div>
} />
</Routes>
</div>
</BrowserRouter>
);
}
export default App;
<Route path="/dashboard" element={<DashboardLayout />}>
: 이것이 부모 라우트입니다.path
에 부모 경로를,element
에 해당 레이아웃 컴포넌트를 지정합니다.<Route index element={<DashboardOverview />} />
:index
속성은 부모 경로(여기서는/dashboard
)로 직접 접근했을 때 렌더링될 기본 자식 컴포넌트를 의미합니다.<Route path="overview" element={<DashboardOverview />} />
:path
에 자식 경로를 명시합니다. 이때 상대 경로로 작성합니다. 즉,overview
는/dashboard/overview
를 의미합니다.
실행 및 확인
npm start
(또는 yarn start
) 명령어를 실행하여 애플리케이션을 시작합니다.
브라우저에서 http://localhost:3000/dashboard
에 접속합니다.
- 사이드바와 함께 "개요 페이지" 콘텐츠가 표시되는지 확인합니다. (이는
index
라우트 덕분입니다.)
사이드바의 "설정" 링크를 클릭합니다.
- URL이
http://localhost:3000/dashboard/settings
로 바뀌고, 레이아웃은 유지된 채 메인 콘텐츠 영역만 "설정 페이지"로 바뀌는 것을 확인합니다.
"분석" 링크도 클릭하여 확인합니다.
브라우저의 뒤로 가기/앞으로 가기 버튼이 정상적으로 작동하는지 확인합니다.
Navbar.js
에 /dashboard
로 이동하는 링크를 추가하여 테스트하는 것도 좋습니다.
중첩 라우팅의 장점
레이아웃 관리 효율성: 여러 하위 페이지에서 공통적으로 사용되는 레이아웃(헤더, 사이드바, 푸터 등)을 부모 라우트 컴포넌트 한 곳에서 관리할 수 있습니다. 각 자식 컴포넌트는 콘텐츠 영역만 신경 쓰면 됩니다.
코드의 응집성 및 재사용성: 관련된 라우트와 컴포넌트들을 논리적으로 묶어 관리할 수 있어 코드의 가독성과 유지보수성이 향상됩니다.
성능 최적화: 공통 레이아웃 부분은 한 번만 렌더링되고, 자식 라우트 변경 시에는 Outlet
내부의 콘텐츠만 다시 렌더링되므로 불필요한 재렌더링을 줄일 수 있습니다.
명확한 UI 계층: URL 구조와 UI 계층이 일관되게 매칭되어 개발자가 애플리케이션의 구조를 쉽게 파악할 수 있습니다.
"중첩 라우팅 소개"는 여기까지입니다. 이 장에서는 중첩 라우팅의 개념, React Router v6에서 이를 구현하는 핵심 컴포넌트인 Outlet
의 역할, 그리고 실제 대시보드 예제를 통해 중첩 라우팅을 설정하고 사용하는 방법을 상세하게 배웠습니다.
이제 여러분은 복잡한 애플리케이션의 UI 레이아웃을 효율적으로 구성하고, URL 구조에 따라 컴포넌트 계층을 일관되게 관리할 수 있는 강력한 라우팅 기법을 이해하게 되었습니다. 다음 장에서는 React Router의 나머지 유용한 기능들과 함께 지금까지 배운 라우팅 지식을 통합하는 실습을 진행하겠습니다.