Вложенные маршруты

Вложенные маршруты (nested routes) — иерархическая структура URL, при которой дочерние маршруты рендерятся внутри родительского компонента через специальный placeholder (Outlet в react-router).

Зачем нужно

Современные приложения имеют устойчивые layout-элементы: боковое меню, шапку, breadcrumbs. Вложенные маршруты позволяют описать эту иерархию один раз: родительский маршрут рендерит layout, дочерние — меняющийся контент через Outlet. Это устраняет дублирование layout-кода и делает структуру URL значимой.

Где используется

  • Дашборды: /dashboard/analytics, /dashboard/users, /dashboard/settings
  • Профиль пользователя: /profile/info, /profile/security, /profile/billing
  • Административные панели с боковым меню
  • Любое приложение с persistent navigation и меняющимся контентом

Nested Routes в React Router v6

import { BrowserRouter, Routes, Route, Outlet, Link } from 'react-router-dom';

// Layout-компонент с Outlet для дочернего контента
function DashboardLayout() {
  return (
    <div className="dashboard">
      <aside className="sidebar">
        <nav>
          <Link to="analytics">Аналитика</Link>
          <Link to="users">Пользователи</Link>
          <Link to="settings">Настройки</Link>
        </nav>
      </aside>
      <main className="content">
        {/* Здесь рендерится активный дочерний маршрут */}
        <Outlet />
      </main>
    </div>
  );
}

function App() {
  return (
    <BrowserRouter>
      <Routes>
        <Route path="/" element={<Home />} />

        {/* Родительский маршрут — DashboardLayout */}
        <Route path="dashboard" element={<DashboardLayout />}>
          {/* index — маршрут по умолчанию (/dashboard) */}
          <Route index element={<DashboardHome />} />

          {/* Дочерние маршруты (/dashboard/analytics и т.д.) */}
          <Route path="analytics" element={<Analytics />} />
          <Route path="users" element={<UsersList />} />
          <Route path="users/:userId" element={<UserDetail />} />
          <Route path="settings" element={<Settings />} />
        </Route>

        <Route path="*" element={<NotFound />} />
      </Routes>
    </BrowserRouter>
  );
}

Три уровня вложенности

function ProfileLayout() {
  return (
    <div>
      <h1>Профиль</h1>
      <nav>
        <Link to="info">Информация</Link>
        <Link to="security">Безопасность</Link>
      </nav>
      <Outlet />
    </div>
  );
}

function SecurityLayout() {
  return (
    <div>
      <h2>Безопасность</h2>
      <nav>
        <Link to="password">Пароль</Link>
        <Link to="2fa">Двухфакторная</Link>
      </nav>
      <Outlet />
    </div>
  );
}

// Роутер с тремя уровнями:
<Route path="profile" element={<ProfileLayout />}>
  <Route index element={<ProfileInfo />} />
  <Route path="security" element={<SecurityLayout />}>
    <Route index element={<SecurityOverview />} />
    <Route path="password" element={<ChangePassword />} />
    <Route path="2fa" element={<TwoFactor />} />
  </Route>
</Route>

// URL: /profile/security/password
// Рендер: ProfileLayout > SecurityLayout > ChangePassword

Доступ к родительским параметрам

// Родительский: /projects/:projectId
// Дочерний:     /projects/:projectId/tasks/:taskId

function TaskDetail() {
  // useParams даёт все параметры из пути, включая родительские
  const { projectId, taskId } = useParams;

  return (
    <div>
      Проект: {projectId}, Задача: {taskId}
    </div>
  );
}

Частые ошибки

  • Забывают <Outlet /> — без него дочерние маршруты не рендерятся; компонент-родитель должен явно указать место для вставки.
  • Используют абсолютные пути в дочерних Link — внутри вложенного роутера to="/dashboard/users" работает, но to="users" (относительный) лучше: не зависит от родительского пути.
  • Дублируют layout — создают одинаковый header/sidebar в каждом компоненте вместо вынесения в родительский layout с Outlet.

Связанные темы

Ресурсы