icon
6장 : React 라우팅 기초

중첩 라우팅 소개


실제 애플리케이션에서 매우 자주 사용되는 강력한 라우팅 기법인 중첩 라우팅(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을 사용하여 자식 라우트 콘텐츠를 렌더링할 위치를 지정합니다.

src/pages/DashboardLayout.js
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;

자식 페이지 컴포넌트 생성

대시보드 하위 메뉴에 해당하는 컴포넌트들을 만듭니다. 이들은 독립적인 컴포넌트입니다.

src/pages/DashboardOverview.js
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;
src/pages/DashboardSettings.js
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;
src/pages/DashboardAnalytics.js
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들을 정의합니다.

src/App.js (수정)
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의 나머지 유용한 기능들과 함께 지금까지 배운 라우팅 지식을 통합하는 실습을 진행하겠습니다.