본문 바로가기

중첩 라우팅 소개

React Router 8.3에서 부모·자식 route를 선언하고 Outlet으로 공통 UI와 경로별 화면의 렌더 경계를 나눕니다.

중첩 라우팅은 URL의 계층과 화면의 계층을 하나의 route tree로 표현하는 방법입니다.

예를 들어 /dashboard/settings에서는 부모 route인 /dashboard와 자식 route인 settings가 한 branch로 매칭됩니다. 부모 element는 공통 레이아웃을 렌더하고, 그 안의 <Outlet />이 매칭된 자식 element의 자리가 됩니다.

부모 레이아웃 안에서 선택한 자식만 Outlet을 채웁니다

REACT ROUTING · URL & RESPONSIBILITY

부모 레이아웃 안에서 선택한 자식만 Outlet을 채웁니다

/dashboard/settings에서는 DashboardLayout과 settings 자식이 한 branch로 함께 매칭됩니다. 부모 URL의 index와 overview·settings·analytics 상대 경로를 구분합니다.

실제 settings 이동 · 공통 UI와 자식의 소유권

부모 /dashboard + 자식 settings
→ /dashboard/settings
DashboardLayout의 main / Outlet
→ DashboardSettings

공통 사이드바·heading은 부모가 소유합니다. /dashboard에서는 index가 DashboardOverview를 렌더하며 index route에는 자식을 둘 수 없습니다. 부모가 branch에 남는 것은 render 횟수 보장이 아닙니다.

dashboard 중첩 route tree부모 path dashboard와 DashboardLayout이 루트이며, 부모 URL의 index와 overview, settings, analytics 상대 path가 네 자식이다. 각 자식 element는 부모 레이아웃의 Outlet에 렌더된다.path="/dashboard"DashboardLayout ·공통 UI + <Outlet />index/dashboardDashboardOverviewpath="overview"/dashboard/overviewDashboardOverviewpath="settings"/dashboard/settingsDashboardSettingspath="analytics"/dashboard/analyticsDashboardAnalytics
dashboard 중첩 route tree부모 path dashboard와 DashboardLayout이 루트이며, 부모 URL의 index와 overview, settings, analytics 상대 path가 네 자식이다. 각 자식 element는 부모 레이아웃의 Outlet에 렌더된다.path="/dashboard"DashboardLayout ·공통 UI + <Outlet />index/dashboardDashboardOverviewpath="overview"/dashboard/overviewDashboardOverviewpath="settings"/dashboard/settingsDashboardSettingspath="analytics"/dashboard/analyticsDashboardAnalytics

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의 세 가지 규칙

  1. 자식 route의 상대 path는 부모 path 뒤에 붙습니다. 부모가 /dashboard이고 자식이 settings이면 최종 pathname은 /dashboard/settings입니다.
  2. index route는 path 없이 부모 URL에서 부모의 Outlet에 렌더되는 기본 자식입니다. index route는 다시 자식을 가질 수 없습니다.
  3. 부모 element에 <Outlet />이 없으면 자식 route가 매칭되어도 그 자식 element가 렌더될 위치가 없습니다.

path가 없는 부모 route는 URL segment를 추가하지 않고 레이아웃 중첩만 만듭니다. 반대로 path는 있지만 element가 없는 부모 route는 자식 URL에 prefix를 더할 뿐 레이아웃을 추가하지 않습니다.


Outlet을 가진 부모 레이아웃

Outlet은 현재 부모 아래에서 매칭된 자식 element를 렌더합니다. 매칭된 자식이 없으면 기본적으로 null을 렌더하므로, 부모 URL에도 본문이 필요하면 index route를 선언합니다.

src/pages/DashboardLayout.jsx
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에서 가져옵니다.

src/App.jsx
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매칭되는 branchOutlet에 렌더되는 element
/dashboard/dashboard → indexDashboardOverview
/dashboard/overview/dashboard → overviewDashboardOverview
/dashboard/settings/dashboard → settingsDashboardSettings
/dashboard/analytics/dashboard → analyticsDashboardAnalytics

여러 route가 branch에 함께 매칭된다는 점이 핵심입니다. /dashboard/settings에서 부모와 자식 중 하나를 고르는 것이 아니라, 부모가 자식을 Outlet에 중첩해 함께 렌더합니다.


route 매칭과 호스트 응답은 다른 경계다

BrowserRouter의 route 선언은 애플리케이션이 시작된 뒤 현재 location을 매칭합니다. 주소창에 /dashboard/settings를 입력한 첫 문서 요청은 먼저 배포 호스트로 갑니다.

직접 진입의 호스트 경계와 중첩 route 매칭 — 상태 추적

/dashboard/settings 문서 요청→host HTML→BrowserRouter→부모+자식 match→Outlet의 현재 실행 역할이 달라진다. client route 추가가 host 404를 자동 고치지 않음을 보강한다.

호스트의 앱 문서 응답 뒤에 중첩 route를 매칭합니다

REACT ROUTING · URL & RESPONSIBILITY

호스트의 앱 문서 응답 뒤에 중첩 route를 매칭합니다

/dashboard/settings를 직접 열 때 브라우저 문서 요청이 먼저입니다. 앱이 실행된 뒤 BrowserRouter가 pathname을 읽고 부모 dashboard와 settings 자식을 구성합니다.

증상으로 경계 찾기 · 내부 이동은 되는데 새로고침만 404

내부 settings Link → branch 정상
GET /dashboard/settings
→ host 404
점검: 호스트의 문서 응답 / fallback

클라이언트 전용 SPA는 해당 document 경로에 앱 문서를 제공해야 합니다. SSR·Framework 통합은 해당 서버가 URL을 처리하며, client Route 추가만으로 host 응답이 바뀌지는 않습니다.

중첩 URL 직접 진입의 호스트와 router 경계브라우저가 dashboard settings URL을 호스트에 요청하고 호스트가 애플리케이션 문서를 응답한다. 애플리케이션이 시작된 뒤 BrowserRouter가 pathname을 읽어 dashboard와 settings branch를 매칭하고 DashboardLayout의 Outlet에 DashboardSettings를 렌더한다. HTTP DOCUMENT DELIVERYCLIENT ROUTER직접 진입/dashboard/settings문서 요청배포 호스트앱 문서 응답rewrite / fallbackBrowserRouterlocation 수신pathnamebranch 매칭dashboard →settings부모 + 자식중첩 렌더DashboardLayout<Outlet />DashboardSettings
중첩 URL 직접 진입의 호스트와 router 경계브라우저가 dashboard settings URL을 호스트에 요청하고 호스트가 애플리케이션 문서를 응답한다. 애플리케이션이 시작된 뒤 BrowserRouter가 pathname을 읽어 dashboard와 settings branch를 매칭하고 DashboardLayout의 Outlet에 DashboardSettings를 렌더한다.HTTP DOCUMENTCLIENT ROUTER직접 진입/dashboard/settings문서 요청배포 호스트앱 문서 응답rewrite / fallbackBrowserRouterlocation 수신pathnamebranch 매칭dashboard → settings부모 + 자식중첩 렌더DashboardLayout<Outlet />DashboardSettings
  1. 브라우저가 문서를 요청합니다

    주소창의 /dashboard/settings 직접 진입은 먼저 해당 pathname의 HTTP 요청입니다.

  2. 호스트가 애플리케이션을 전달합니다

    클라이언트 전용 SPA는 배포 환경에 맞는 rewrite 또는 fallback이 필요합니다.

  3. BrowserRouter가 location을 읽습니다

    클라이언트 route 매칭은 애플리케이션이 시작된 다음 단계입니다.

  4. route branch를 매칭합니다

    dashboard 부모와 settings 자식이 함께 선택됩니다.

  5. 부모와 자식을 중첩 렌더합니다

    DashboardLayout의 Outlet에 DashboardSettings가 들어갑니다.

호스트 경계

첫 요청의 문서 응답, SPA fallback, SSR 처리는 배포 환경의 책임입니다. 클라이언트 route 선언만으로 서버 404가 사라지지는 않습니다.

router 경계

앱이 실행된 뒤 React Router가 location과 route tree를 비교하고 매칭 branch의 element를 각 Outlet에 구성합니다.

이 예의 조건

  • 원도표의 전체 구조와 설명은 위 관계도와 표에서 유지합니다. 아래 인쇄 표에도 원도표의 관계·조건·라벨·설명·주석을 보존합니다.
  • 두 관찰 칸의 위치는 고정입니다. 관찰 상태는 해당 순간의 값이며, 동작·이유는 그 조건에서의 판정과 변화 원인입니다. 독립 비교와 실패 분기는 별도 조건으로 읽고 모든 분기를 한 요청의 필수 실행 순서로 이어 읽지 않습니다.
관계도의 보충 설명
원도표의 전체 관계·조건·설명
구분내용
원도표 조건·주석브라우저가 문서를 요청합니다. 주소창의 /dashboard/settings 직접 진입은 먼저 해당 pathname의 HTTP 요청입니다.
원도표 조건·주석호스트가 애플리케이션을 전달합니다. 클라이언트 전용 SPA는 배포 환경에 맞는 rewrite 또는 fallback이 필요합니다.
원도표 설명BrowserRouter가 location을 읽습니다
원도표 조건·주석BrowserRouter가 location을 읽습니다. 클라이언트 route 매칭은 애플리케이션이 시작된 다음 단계입니다.
원도표 조건·주석route branch를 매칭합니다. dashboard 부모와 settings 자식이 함께 선택됩니다.
원도표 설명DashboardLayout의 Outlet에 DashboardSettings가 들어갑니다.
원도표 조건·주석부모와 자식을 중첩 렌더합니다. DashboardLayout의 Outlet에 DashboardSettings가 들어갑니다.
원도표 설명앱이 실행된 뒤 React Router가 location과 route tree를 비교하고 매칭 branch의 element를 각 Outlet에 구성합니다.
전체 상태와 변화 근거
장면현재 관찰값설명변화 이유
중첩 직접 주소host: GET /dashboard/settings / router: 실행 전브라우저가 /dashboard/settings 문서를 요청한다호스트는 중첩 경로에도 문서를 전달해야 한다
route branchparent: dashboard / child: settings앱 시작 뒤 dashboard와 settings의 부모·자식 branch를 고른다client 선언만으로 host의 rewrite를 생성하지 않는다
중첩 렌더layout: 공통 UI 유지 / Outlet: SettingsDashboardLayout의 Outlet에 DashboardSettings를 표시한다문서 전달과 router 표시의 책임이 다르다

직접 진입의 호스트 경계와 중첩 route 매칭 — 상태 추적

/dashboard/settings 문서 요청→host HTML→BrowserRouter→부모+자식 match→Outlet의 현재 실행 역할이 달라진다. client route 추가가 host 404를 자동 고치지 않음을 보강한다.

호스트의 앱 문서 응답 뒤에 중첩 route를 매칭합니다

REACT ROUTING · URL & RESPONSIBILITY

호스트의 앱 문서 응답 뒤에 중첩 route를 매칭합니다

/dashboard/settings를 직접 열 때 브라우저 문서 요청이 먼저입니다. 앱이 실행된 뒤 BrowserRouter가 pathname을 읽고 부모 dashboard와 settings 자식을 구성합니다.

증상으로 경계 찾기 · 내부 이동은 되는데 새로고침만 404

내부 settings Link → branch 정상
GET /dashboard/settings
→ host 404
점검: 호스트의 문서 응답 / fallback

클라이언트 전용 SPA는 해당 document 경로에 앱 문서를 제공해야 합니다. SSR·Framework 통합은 해당 서버가 URL을 처리하며, client Route 추가만으로 host 응답이 바뀌지는 않습니다.

중첩 URL 직접 진입의 호스트와 router 경계브라우저가 dashboard settings URL을 호스트에 요청하고 호스트가 애플리케이션 문서를 응답한다. 애플리케이션이 시작된 뒤 BrowserRouter가 pathname을 읽어 dashboard와 settings branch를 매칭하고 DashboardLayout의 Outlet에 DashboardSettings를 렌더한다. HTTP DOCUMENT DELIVERYCLIENT ROUTER직접 진입/dashboard/settings문서 요청배포 호스트앱 문서 응답rewrite / fallbackBrowserRouterlocation 수신pathnamebranch 매칭dashboard →settings부모 + 자식중첩 렌더DashboardLayout<Outlet />DashboardSettings
중첩 URL 직접 진입의 호스트와 router 경계브라우저가 dashboard settings URL을 호스트에 요청하고 호스트가 애플리케이션 문서를 응답한다. 애플리케이션이 시작된 뒤 BrowserRouter가 pathname을 읽어 dashboard와 settings branch를 매칭하고 DashboardLayout의 Outlet에 DashboardSettings를 렌더한다.HTTP DOCUMENTCLIENT ROUTER직접 진입/dashboard/settings문서 요청배포 호스트앱 문서 응답rewrite / fallbackBrowserRouterlocation 수신pathnamebranch 매칭dashboard → settings부모 + 자식중첩 렌더DashboardLayout<Outlet />DashboardSettings
  1. 브라우저가 문서를 요청합니다

    주소창의 /dashboard/settings 직접 진입은 먼저 해당 pathname의 HTTP 요청입니다.

  2. 호스트가 애플리케이션을 전달합니다

    클라이언트 전용 SPA는 배포 환경에 맞는 rewrite 또는 fallback이 필요합니다.

  3. BrowserRouter가 location을 읽습니다

    클라이언트 route 매칭은 애플리케이션이 시작된 다음 단계입니다.

  4. route branch를 매칭합니다

    dashboard 부모와 settings 자식이 함께 선택됩니다.

  5. 부모와 자식을 중첩 렌더합니다

    DashboardLayout의 Outlet에 DashboardSettings가 들어갑니다.

호스트 경계

첫 요청의 문서 응답, SPA fallback, SSR 처리는 배포 환경의 책임입니다. 클라이언트 route 선언만으로 서버 404가 사라지지는 않습니다.

router 경계

앱이 실행된 뒤 React Router가 location과 route tree를 비교하고 매칭 branch의 element를 각 Outlet에 구성합니다.

이 예의 조건

  • 원도표의 전체 구조와 설명은 위 관계도와 표에서 유지합니다. 아래 인쇄 표에도 원도표의 관계·조건·라벨·설명·주석을 보존합니다.
  • 두 관찰 칸의 위치는 고정입니다. 관찰 상태는 해당 순간의 값이며, 동작·이유는 그 조건에서의 판정과 변화 원인입니다. 독립 비교와 실패 분기는 별도 조건으로 읽고 모든 분기를 한 요청의 필수 실행 순서로 이어 읽지 않습니다.

관계도의 보충 설명

원도표의 전체 관계·조건·설명
구분내용
원도표 조건·주석브라우저가 문서를 요청합니다. 주소창의 /dashboard/settings 직접 진입은 먼저 해당 pathname의 HTTP 요청입니다.
원도표 조건·주석호스트가 애플리케이션을 전달합니다. 클라이언트 전용 SPA는 배포 환경에 맞는 rewrite 또는 fallback이 필요합니다.
원도표 설명BrowserRouter가 location을 읽습니다
원도표 조건·주석BrowserRouter가 location을 읽습니다. 클라이언트 route 매칭은 애플리케이션이 시작된 다음 단계입니다.
원도표 조건·주석route branch를 매칭합니다. dashboard 부모와 settings 자식이 함께 선택됩니다.
원도표 설명DashboardLayout의 Outlet에 DashboardSettings가 들어갑니다.
원도표 조건·주석부모와 자식을 중첩 렌더합니다. DashboardLayout의 Outlet에 DashboardSettings가 들어갑니다.
원도표 설명앱이 실행된 뒤 React Router가 location과 route tree를 비교하고 매칭 branch의 element를 각 Outlet에 구성합니다.
전체 상태와 변화 근거
장면현재 관찰값설명변화 이유
중첩 직접 주소host: GET /dashboard/settings / router: 실행 전브라우저가 /dashboard/settings 문서를 요청한다호스트는 중첩 경로에도 문서를 전달해야 한다
route branchparent: dashboard / child: settings앱 시작 뒤 dashboard와 settings의 부모·자식 branch를 고른다client 선언만으로 host의 rewrite를 생성하지 않는다
중첩 렌더layout: 공통 UI 유지 / Outlet: SettingsDashboardLayout의 Outlet에 DashboardSettings를 표시한다문서 전달과 router 표시의 책임이 다르다

클라이언트 전용 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 문제와 배포 문제를 분리할 수 있습니다. 세 자식 화면을 준비하기

앞의 route 선언을 실행하려면 import한 자식 파일도 있어야 합니다. 개요 파일을 다음처럼 만들고, 설정·분석도 각 파일 이름에 맞는 기본 export와 화면 제목을 둡니다.

src/pages/DashboardOverview.jsx
export default function DashboardOverview() {
  return <p>개요 화면</p>;
}

DashboardSettings.jsx의 기본 export는 DashboardSettings이며 설정 화면, DashboardAnalytics.jsx는 DashboardAnalytics이며 분석 화면을 반환합니다. 별도 데이터 요청 없이도 메뉴와 Outlet의 연결을 먼저 확인할 수 있습니다.

이 절의 App은 BrowserRouter를 이미 갖고 있습니다. 진입 파일은 createRoot로 <App />을 렌더하며 같은 라우터를 다시 감싸지 않습니다. 공통 레이아웃을 둔다는 설계와 라우터 provider를 중복한다는 구현은 별개입니다.

규칙 하나를 지우면 무엇이 사라질까요?

두 변경은 각각 원래 코드에서 따로 시험한다고 가정합니다.

  1. index route만 제거하고 /dashboard와 /dashboard/overview에 진입합니다.
  2. route 선언은 원래대로 두고 DashboardLayout의 <Outlet />만 제거한 뒤 /dashboard/settings에 진입합니다.

각 경우에 사이드 메뉴·대시보드 제목·자식 본문 중 무엇이 보이는지와 URL 매칭 자체가 실패했는지를 구별합니다.

매칭과 표시 자리를 구별하는 해설

첫 번째의 /dashboard는 부모를 렌더하지만 기본 자식이 없으므로 사이드 메뉴와 대시보드 제목만 남고 Outlet은 비어 있습니다. 별도로 선언된 /dashboard/overview에서는 여전히 개요 자식이 보입니다. index 삭제가 모든 개요 경로를 지우지는 않습니다.

두 번째는 /dashboard/settings의 부모·자식 branch가 여전히 매칭됩니다. 하지만 부모가 Outlet을 렌더하지 않아 설정 본문이 표시되지 않습니다. 공통 메뉴와 제목은 남습니다. 이 실패를 호스트의 HTTP 404와 혼동하거나 route path를 다시 추가해서 해결할 수는 없습니다.