본문 바로가기

중첩 라우팅 소개

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

중첩 라우팅(nested routing)은 URL의 계층과 화면의 계층을 하나의 라우트 트리로 나타내는 방법입니다. 예를 들어 /dashboard/settings에서는 부모 라우트인 /dashboard와 자식 라우트인 settings가 하나의 가지로 함께 맞습니다. 부모 element는 공통 레이아웃을 렌더링하고, 그 안의 <Outlet />이 맞은 자식 element가 들어갈 자리가 됩니다. 이 글도 앞 글처럼 React Router 8.5의 Declarative mode를 기준으로 합니다.

부모 레이아웃 안에서 선택한 자식만 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를 중첩합니다.

위 그림은 이 글의 대시보드 라우트 트리입니다. 루트는 path="/dashboard"와 공통 UI, <Outlet />을 가진 DashboardLayout이고, 그 아래에 네 자식이 있습니다. 부모 URL의 기본 자식인 index는 /dashboard에서 DashboardOverview를, path="overview"는 /dashboard/overview에서 같은 DashboardOverview를, path="settings"는 /dashboard/settings에서 DashboardSettings를, path="analytics"는 /dashboard/analytics에서 DashboardAnalytics를 부모의 Outlet에 렌더링합니다.

그림 위쪽의 예처럼 부모 /dashboard와 자식 settings가 합쳐져 /dashboard/settings가 되면, DashboardLayout의 main 안 Outlet에 DashboardSettings가 들어갑니다. 공통 사이드바와 제목은 부모가 맡으며, 그림의 설명처럼 부모 element를 자식 가운데 하나로 바꾸는 것이 아니라 부모가 렌더링한 Outlet에 자식 element를 중첩하는 것입니다. index 라우트에는 자식을 둘 수 없고, 부모가 가지에 남아 있다고 해서 렌더링 횟수가 보장되는 것도 아닙니다.


중첩 route의 세 가지 규칙

중첩 라우트에는 세 가지 규칙이 있습니다. 첫째, 자식 라우트의 상대 path는 부모 path 뒤에 붙습니다. 부모가 /dashboard이고 자식이 settings이면 최종 경로는 /dashboard/settings입니다. 둘째, index 라우트는 path 없이 부모 URL에서 부모의 Outlet에 렌더링되는 기본 자식이며, 자기 아래에 다시 자식을 가질 수 없습니다. 셋째, 부모 element에 <Outlet />이 없으면 자식 라우트가 맞아도 그 자식 element가 렌더링될 자리가 없습니다.

또 path가 없는 부모 라우트는 URL에 조각을 더하지 않고 레이아웃의 중첩만 만들며, 반대로 path는 있지만 element가 없는 부모 라우트는 자식 URL에 앞부분을 더할 뿐 레이아웃을 더하지 않습니다.


Outlet을 가진 부모 레이아웃

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

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" 규칙에 따라 현재의 라우트 계층을 기준으로 해석되어 /dashboard/settings가 됩니다. 반면 /로 시작하는 to="/contact"는 같은 출처 안의 절대 경로입니다.

더 깊이: ..가 거슬러 올라가는 단위

relative="route"와 relative="path"의 차이는 to=".."처럼 위로 올라가는 링크에서 드러납니다. React Router의 Link 문서는 부모 라우트의 패턴이 "blog"이고 자식의 패턴이 "blog/:slug/edit"인 예를 듭니다. 기본값인 route에서는 ..가 현재 라우트 패턴의 조각을 모두 걷어 내 /blog로 가지만, path에서는 URL 조각 하나만 걷어 내 /blog/:slug 자리, 예를 들어 /blog/hello로 갑니다. 그래서 편집 화면의 “상세로 돌아가기”를 ..로 만들었다면, 라우트를 어떻게 나눴는지에 따라 목록으로 가 버릴 수 있습니다.

같은 문서는 index 라우트와 경로 없는 레이아웃 라우트가 상대 경로의 계산에서 빠진다고도 설명합니다. 그래서 DashboardOverview 같은 index 라우트 안에서 to="settings"를 써도, index 라우트는 경로를 더하지 않으므로 부모의 /dashboard를 기준으로 계산되어 /dashboard/settings가 됩니다. 경로 없는 레이아웃 라우트를 중간에 끼워 넣어도 상대 링크의 결과가 바뀌지 않는 것도 같은 이유입니다.


부모와 자식 route 선언

React Router 8에서는 Declarative mode의 컴포넌트를 react-router에서 가져옵니다. 예전 자료처럼 react-router-dom에서 가져오지 않고, BrowserRouter, Routes, Route를 모두 이 패키지 하나에서 가져옵니다. 아래 선언은 /dashboard 아래에 index와 자식 세 개를 두고, 어느 경로와도 맞지 않는 주소는 맨 아래의 * 라우트가 받습니다. index와 overview가 같은 DashboardOverview를 가리키므로, /dashboard와 /dashboard/overview는 같은 내용을 보여 줍니다.

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맞는 가지Outlet에 렌더링되는 element
/dashboard/dashboard → indexDashboardOverview
/dashboard/overview/dashboard → overviewDashboardOverview
/dashboard/settings/dashboard → settingsDashboardSettings
/dashboard/analytics/dashboard → analyticsDashboardAnalytics

핵심은 여러 라우트가 하나의 가지로 함께 맞는다는 점입니다. /dashboard/settings에서는 부모와 자식 가운데 하나를 고르는 것이 아니라, 부모가 자식을 Outlet에 넣어 함께 렌더링합니다.

이 선언을 실행하려면 가져온 자식 파일도 있어야 합니다. 개요 파일을 다음처럼 만듭니다.

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

DashboardSettings.jsx는 DashboardSettings를 기본으로 내보내며 “설정 화면”을, DashboardAnalytics.jsx는 DashboardAnalytics를 내보내며 “분석 화면”을 돌려주게 만듭니다. 별도의 데이터 요청 없이도 메뉴와 Outlet의 연결을 먼저 확인할 수 있습니다. 이 글의 App은 이미 BrowserRouter를 가지고 있으므로, 진입 파일은 createRoot로 <App />만 렌더링하고 같은 라우터를 다시 감싸지 않습니다. 공통 레이아웃을 둔다는 설계와 라우터를 두 번 감싸는 구현은 별개입니다.


route 매칭과 호스트 응답은 다른 단계입니다

BrowserRouter의 라우트 선언은 앱이 시작된 뒤에 현재 위치를 맞춰 봅니다. 그런데 주소창에 /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 표시의 책임이 다르다

위 그림은 직접 진입에서 호스트가 앱 문서를 돌려준 뒤에야 중첩 라우트가 맞춰지는 순서를 두 영역으로 나눠 보여 줍니다. 왼쪽의 HTTP 문서 전달 영역에서는 브라우저가 /dashboard/settings의 문서를 요청하고, 배포 호스트가 재작성(rewrite)이나 대체 응답(fallback)으로 앱 문서를 돌려줍니다. 오른쪽의 클라이언트 라우터 영역에서는 앱이 시작된 뒤 BrowserRouter가 위치를 받아 dashboard에서 settings로 이어지는 가지를 맞추고, DashboardLayout의 <Outlet />에 DashboardSettings를 넣어 중첩 렌더링합니다.

그림 위쪽의 예는 문제가 어디서 생겼는지 증상으로 찾는 방법입니다. 내부의 settings 링크로는 가지가 정상적으로 맞는데 GET /dashboard/settings로 새로고침할 때만 호스트가 404를 낸다면, 라우트 트리가 아니라 호스트의 문서 응답과 대체 응답 설정을 점검해야 합니다. 그림의 설명처럼 첫 요청의 문서 응답과 SPA의 대체 응답, SSR 처리는 배포 환경이 맡고, 앱이 실행된 뒤 위치와 라우트 트리를 비교해 각 Outlet에 element를 구성하는 일은 React Router가 맡습니다.

그래서 클라이언트 전용 SPA라면 중첩된 경로에도 호스트가 앱 문서를 돌려주도록 재작성이나 대체 응답을 설정해야 합니다. 서버 렌더링이나 Framework mode를 쓴다면 그 서버 통합이 URL을 처리할 수 있습니다. React Router에 클라이언트 라우트를 더하는 것만으로 서버의 404가 저절로 해결되지는 않습니다.


렌더 결과 확인

앱을 실행하고 /dashboard에 직접 들어가 공통 레이아웃과 index 라우트의 “개요 화면”이 함께 보이는지 확인합니다. 상대 링크로 설정에 이동하면 URL이 /dashboard/settings가 되고 Outlet의 자식 화면이 “설정 화면”으로 바뀌어야 합니다. 개요와 분석을 오가며 공통 레이아웃이 계속 남아 있는지, 링크의 활성 상태가 맞는지도 봅니다.

새로고침과 주소창의 직접 진입도 모두 시험합니다. 여기서만 404가 난다면 라우트 트리보다 호스트의 문서 응답 설정을 먼저 점검합니다. 마지막으로 뒤로 가기와 앞으로 가기로 history 항목과 화면이 함께 되살아나는지 확인합니다.


중첩 라우팅을 쓰는 이유

중첩 라우팅을 쓰면 헤더와 사이드바, 탭처럼 여러 자식 화면이 공유하는 UI를 부모 라우트 한곳에 둘 수 있고, URL의 계층과 화면의 역할을 같은 라우트 트리에서 함께 읽을 수 있습니다. index 라우트로 부모 URL의 기본 화면을 분명히 정하고, 상대 경로와 상대 링크로 하위 경로를 그 부분 안에서 관리합니다. 더 깊은 레이아웃이 필요하면 자식 element에도 Outlet을 두어 한 단계 더 중첩할 수 있습니다.

다만 중첩 라우팅은 렌더링 횟수를 보장하는 성능 기능이 아닙니다. 부모 라우트가 가지에 계속 남아 있더라도, 실제로 렌더링하고 커밋하는지는 React의 일반적인 state·props·context·재조정 규칙을 따릅니다. 핵심 이점은 공통 UI와 자식 화면이 각각 무엇을 맡는지 분명히 하는 데 있습니다.

중첩 라우팅을 읽을 때는 경로, 맞는 가지, 각 부모의 Outlet, 최종 자식 element의 순서로 확인합니다. 직접 진입의 문제까지 다룰 때는 그 앞에 호스트가 앱 문서를 돌려줬는지를 한 단계 더 붙이면, 라우트의 문제와 배포의 문제를 나눠 볼 수 있습니다.


정리

  • 중첩 라우트의 자식 path는 부모 path 뒤에 붙고, 부모와 자식이 하나의 가지로 함께 맞습니다.
  • 부모 element의 <Outlet />이 맞은 자식 element의 자리이며, 없으면 자식이 맞아도 보이지 않습니다.
  • index 라우트는 부모 URL의 기본 자식이며, 자기 아래에 자식을 둘 수 없습니다.
  • 레이아웃 안의 to="settings" 같은 상대 링크는 현재 라우트 계층을 기준으로 해석됩니다.
  • 직접 진입은 먼저 호스트가 앱 문서를 돌려줘야 하며, 클라이언트 라우트를 더하는 것만으로 서버의 404가 사라지지 않습니다.

연습 문제

1. index 라우트를 지우면

원래 코드에서 index 라우트만 지우고 /dashboard와 /dashboard/overview에 각각 들어갑니다. 사이드 메뉴와 대시보드 제목, 자식 본문 가운데 무엇이 보일까요?

해설 보기

/dashboard는 부모를 렌더링하지만 기본 자식이 없으므로, 사이드 메뉴와 대시보드 제목만 남고 Outlet은 비어 있습니다. 따로 선언된 /dashboard/overview에서는 여전히 “개요 화면”이 보입니다. index를 지운다고 모든 개요 경로가 사라지지는 않습니다.

2. Outlet을 지우면

라우트 선언은 원래대로 두고 DashboardLayout의 <Outlet />만 지운 뒤 /dashboard/settings에 들어갑니다. 무엇이 보이고, URL 매칭 자체가 실패한 것일까요?

해설 보기

/dashboard/settings의 부모와 자식 가지는 여전히 맞습니다. 하지만 부모가 Outlet을 렌더링하지 않으므로 “설정 화면”은 보이지 않고, 공통 메뉴와 제목만 남습니다. 이 증상을 호스트의 HTTP 404와 혼동하거나, 라우트 경로를 다시 더해서 해결할 수는 없습니다.

3. /dashboard의 활성 링크

원래 코드로 /dashboard에 들어갔을 때, 사이드 메뉴의 세 NavLink 가운데 활성으로 표시되는 것이 있을까요?

해설 보기

없습니다. 화면에는 index 라우트의 “개요 화면”이 보이지만, “개요” 링크의 to="overview"는 /dashboard/overview를 가리키므로 현재 경로 /dashboard와 맞지 않습니다. 개요가 기본 화면일 때도 그 링크를 활성으로 보이게 하려면, 링크를 to="."과 end로 index를 가리키게 하거나 기본 화면의 경로를 하나로 정하는 식으로 설계를 맞춰야 합니다.

다음 글 간단한 다중 경로 SPA 만들기에서는 6장에서 배운 라우팅을 모아 여러 화면을 가진 작은 SPA를 직접 만듭니다.