중첩 라우팅 소개
React Router 8.3에서 부모·자식 route를 선언하고 Outlet으로 공통 UI와 경로별 화면의 렌더 경계를 나눕니다.
중첩 라우팅은 URL의 계층과 화면의 계층을 하나의 route tree로 표현하는 방법입니다.
예를 들어 /dashboard/settings에서는 부모 route인 /dashboard와 자식 route인 settings가 한 branch로 매칭됩니다. 부모 element는 공통 레이아웃을 렌더하고, 그 안의 <Outlet />이 매칭된 자식 element의 자리가 됩니다.
React Router 8.3 · nested route tree
URL은 하나의 route branch를 고르고, branch의 element들은 중첩해 렌더됩니다. 부모 DashboardLayout이 공통 UI를 소유하고 현재 자식 element가 Outlet 자리를 채웁니다.
DashboardLayout/dashboard · Outlet 경계
index · 부모 URL의 기본 자식
/dashboard에서 DashboardOverview를 Outlet에 렌더합니다.
path="overview"
/dashboard/overview에서 DashboardOverview를 렌더합니다.
path="settings"
/dashboard/settings에서 DashboardSettings를 렌더합니다.
path="analytics"
/dashboard/analytics에서 DashboardAnalytics를 렌더합니다.
/dashboard/settings에서는 부모와 settings 자식이 함께 매칭됩니다. 부모 element를 자식 중 하나로 교체하는 것이 아니라, 부모가 렌더한 Outlet에 자식 element를 중첩합니다.
중첩 route의 세 가지 규칙
- 자식 route의 상대
path는 부모path뒤에 붙습니다. 부모가/dashboard이고 자식이settings이면 최종 pathname은/dashboard/settings입니다. - index route는
path없이 부모 URL에서 부모의Outlet에 렌더되는 기본 자식입니다. index route는 다시 자식을 가질 수 없습니다. - 부모 element에
<Outlet />이 없으면 자식 route가 매칭되어도 그 자식 element가 렌더될 위치가 없습니다.
path가 없는 부모 route는 URL segment를 추가하지 않고 레이아웃 중첩만 만듭니다. 반대로 path는 있지만 element가 없는 부모 route는 자식 URL에 prefix를 더할 뿐 레이아웃을 추가하지 않습니다.
Outlet을 가진 부모 레이아웃
Outlet은 현재 부모 아래에서 매칭된 자식 element를 렌더합니다. 매칭된 자식이 없으면 기본적으로 null을 렌더하므로, 부모 URL에도 본문이 필요하면 index route를 선언합니다.
import { NavLink, Outlet } from "react-router";
export default function DashboardLayout() {
return (
<div className="dashboard">
<aside>
<nav aria-label="대시보드 메뉴">
<NavLink to="overview">개요</NavLink>
<NavLink to="settings">설정</NavLink>
<NavLink to="analytics">분석</NavLink>
</nav>
</aside>
<main>
<h2>대시보드</h2>
<Outlet />
</main>
</div>
);
}여기서 to="settings" 같은 상대 링크는 기본 relative="route" 규칙에 따라 현재 route 계층에서 해석됩니다. /로 시작하는 to="/contact"는 origin 안의 절대 pathname입니다.
부모와 자식 route 선언
React Router 8에서는 선언형 라우팅의 컴포넌트를 react-router에서 가져옵니다.
import { BrowserRouter, Route, Routes } from "react-router";
import DashboardLayout from "./pages/DashboardLayout";
import DashboardOverview from "./pages/DashboardOverview";
import DashboardSettings from "./pages/DashboardSettings";
import DashboardAnalytics from "./pages/DashboardAnalytics";
export default function App() {
return (
<BrowserRouter>
<Routes>
<Route path="/dashboard" element={<DashboardLayout />}>
<Route index element={<DashboardOverview />} />
<Route path="overview" element={<DashboardOverview />} />
<Route path="settings" element={<DashboardSettings />} />
<Route path="analytics" element={<DashboardAnalytics />} />
</Route>
<Route path="*" element={<p>페이지를 찾을 수 없습니다.</p>} />
</Routes>
</BrowserRouter>
);
}이 선언이 만드는 결과는 다음과 같습니다.
| pathname | 매칭되는 branch | Outlet에 렌더되는 element |
|---|---|---|
/dashboard | /dashboard → index | DashboardOverview |
/dashboard/overview | /dashboard → overview | DashboardOverview |
/dashboard/settings | /dashboard → settings | DashboardSettings |
/dashboard/analytics | /dashboard → analytics | DashboardAnalytics |
여러 route가 branch에 함께 매칭된다는 점이 핵심입니다. /dashboard/settings에서 부모와 자식 중 하나를 고르는 것이 아니라, 부모가 자식을 Outlet에 중첩해 함께 렌더합니다.
route 매칭과 호스트 응답은 다른 경계다
BrowserRouter의 route 선언은 애플리케이션이 시작된 뒤 현재 location을 매칭합니다. 주소창에 /dashboard/settings를 입력한 첫 문서 요청은 먼저 배포 호스트로 갑니다.
React Router 8.3 · host and match boundary
첫 문서 요청과 클라이언트 route 매칭은 서로 다른 책임입니다. 호스트가 애플리케이션을 전달한 뒤에야 BrowserRouter가 location을 읽고 중첩 branch를 렌더합니다.
브라우저가 문서를 요청합니다
주소창의
/dashboard/settings직접 진입은 먼저 해당 pathname의 HTTP 요청입니다.호스트가 애플리케이션을 전달합니다
클라이언트 전용 SPA는 배포 환경에 맞는 rewrite 또는 fallback이 필요합니다.
BrowserRouter가 location을 읽습니다클라이언트 route 매칭은 애플리케이션이 시작된 다음 단계입니다.
route branch를 매칭합니다
dashboard부모와settings자식이 함께 선택됩니다.부모와 자식을 중첩 렌더합니다
DashboardLayout의Outlet에DashboardSettings가 들어갑니다.
호스트 경계
첫 요청의 문서 응답, SPA fallback, SSR 처리는 배포 환경의 책임입니다. 클라이언트 route 선언만으로 서버 404가 사라지지는 않습니다.
router 경계
앱이 실행된 뒤 React Router가 location과 route tree를 비교하고 매칭 branch의 element를 각 Outlet에 구성합니다.
클라이언트 전용 SPA라면 호스트가 중첩 pathname에도 애플리케이션 문서를 반환하도록 rewrite 또는 fallback을 설정해야 합니다. 서버 렌더링이나 Framework Mode를 사용한다면 해당 서버 통합이 URL을 처리할 수 있습니다. React Router의 클라이언트 route만 추가해도 서버의 404가 자동으로 해결되는 것은 아닙니다.
렌더 결과 확인
애플리케이션을 실행하고 /dashboard에 직접 진입합니다. 공통 레이아웃과 index route의 DashboardOverview가 함께 보이는지 확인합니다.
상대 링크로 settings에 이동합니다. URL이 /dashboard/settings가 되고 Outlet의 자식 화면이 DashboardSettings로 바뀌는지 확인합니다.
overview와 analytics를 오가며 공통 레이아웃이 각 branch에 계속 포함되고 링크의 활성 상태가 맞는지 확인합니다.
페이지 새로고침과 주소창 직접 진입을 모두 시험합니다. 여기서만 404가 난다면 route tree보다 호스트의 문서 응답 설정을 먼저 점검합니다.
뒤로 가기와 앞으로 가기로 history entry와 화면이 함께 복원되는지 확인합니다.
중첩 라우팅을 쓰는 이유
- 헤더, 사이드바, 탭처럼 여러 자식 화면이 공유하는 UI를 부모 route 한 곳에 둡니다.
- URL 계층과 화면 책임을 같은 route tree에서 읽을 수 있습니다.
- index route로 부모 URL의 기본 화면을 명시하고, 상대 path와 링크로 하위 경로를 지역적으로 관리합니다.
- 더 깊은 레이아웃이 필요하면 자식 element에도
Outlet을 두어 한 단계 더 중첩할 수 있습니다.
중첩 라우팅은 렌더 횟수를 보장하는 성능 기능이 아닙니다. 부모 route가 계속 branch에 포함되더라도 실제 render와 commit 여부는 React의 일반적인 상태·props·context·reconciliation 규칙을 따릅니다. 핵심 이점은 공통 UI와 자식 화면의 소유 경계를 명시하는 데 있습니다.
중첩 라우팅을 읽을 때는 pathname → 매칭 branch → 각 부모의 Outlet → 최종 자식 element 순서로 확인합니다. 직접 진입 문제까지 다룰 때는 그 앞에 호스트가 애플리케이션 문서를 반환했는가를 한 단계 더 붙이면 route 문제와 배포 문제를 분리할 수 있습니다.