Олег Марков
Вложенные маршруты nested-routes - как построить удобную и масштабируемую навигацию
Введение
Вложенные маршруты (nested routes) помогают организовать навигацию в веб‑приложении так, чтобы она отражала реальную иерархию интерфейса и данных. Если вы создаете сложное SPA‑приложение с несколькими уровнями страниц, без вложенных маршрутов очень быстро появляется хаос: дублирование кода, сложное состояние и запутанные URL.
Смотрите, здесь важно понимать простую идею. Вложенный маршрут — это маршрут, который живет внутри другого маршрута и отображается в его «области». На уровне URL это выглядит как иерархия путей, например:
- /dashboard
- /dashboard/users
- /dashboard/users/42
А на уровне интерфейса — как экран «Dashboard», внутри которого есть область для списка пользователей, а внутри нее — карточка конкретного пользователя.
В статье я покажу на примерах, как это устроено в типичном SPA на React с использованием React Router v6. Если вы работаете с Vue Router, Angular Router или другим похожим решением, общие принципы будут такими же: меняется синтаксис, но не сама концепция nested-routes.
Что такое вложенные маршруты и зачем они нужны
Логическая иерархия страниц
Когда у вас есть сущность верхнего уровня и вложенные сущности, естественно отразить это в маршрутах:
- Раздел «Админка»
- Список пользователей
- Детальная страница пользователя
- Настройки
- Отчеты
- Список пользователей
Такую структуру удобно представить как дерево:
- /admin
- /admin/users
- /admin/users/:id
- /admin/settings
- /admin/reports
- /admin/users
Вложенные маршруты позволяют:
- Разделить код на уровни — родительский маршрут отвечает за общий каркас страницы, а дочерние — за конкретное содержимое.
- Переиспользовать общий layout — шапка, меню, подвал, боковая панель.
- Управлять общим состоянием на уровне родителя — авторизация, загрузка данных, контекст.
Связь UI и структуры URL
Важно, что nested-routes поддерживают естественную связь:
- Иерархия компонентов в интерфейсе.
- Иерархия путей в URL.
Например, если вы отображаете компонент DashboardLayout, внутри него — навигацию, а под ней — область для контента, то дочерние маршруты будут как раз описывать, что отображается в этой области.
Это делает навигацию для пользователя предсказуемой, а код — более структурированным.
Базовая конфигурация вложенных маршрутов на примере React Router v6
Давайте разберемся на конкретном примере. Ниже — простейшее приложение с разделом /dashboard, внутри которого живут другие страницы.
Структура приложения
Представим такую структуру:
/— главная страница./dashboard— общий layout для кабинета./dashboard/profile— профиль./dashboard/settings— настройки.
Сейчас вы увидите, как это выглядит в коде.
// App.jsx
import { BrowserRouter, Routes, Route, Link } from "react-router-dom";
// Главная страница
function HomePage() {
return (
<div>
{/* Ссылка в раздел дашборда */}
<h1>Главная</h1>
<Link to="/dashboard">Перейти в кабинет</Link>
</div>
);
}
// Layout для dashboard - родительский маршрут
import { Outlet } from "react-router-dom";
function DashboardLayout() {
return (
<div>
{/* Общие части интерфейса дашборда */}
<h1>Dashboard</h1>
{/* Локальное меню для дочерних маршрутов */}
<nav>
<Link to="profile">Профиль</Link> |{" "}
<Link to="settings">Настройки</Link>
</nav>
{/* Здесь будут рендериться дочерние маршруты */}
<div style={{ marginTop: 20 }}>
<Outlet />
</div>
</div>
);
}
// Конкретные страницы внутри дашборда
function ProfilePage() {
return <div>Страница профиля</div>;
}
function SettingsPage() {
return <div>Страница настроек</div>;
}
export default function App() {
return (
// Корневой роутер
<BrowserRouter>
<Routes>
{/* Обычный маршрут для главной страницы */}
<Route path="/" element={<HomePage />} />
{/* Родительский маршрут для dashboard */}
<Route path="/dashboard" element={<DashboardLayout />}>
{/* Вложенные маршруты с относительными путями */}
<Route path="profile" element={<ProfilePage />} />
<Route path="settings" element={<SettingsPage />} />
</Route>
</Routes>
</BrowserRouter>
);
}
Обратите внимание на несколько ключевых моментов:
В родительском компоненте
DashboardLayoutмы используемOutlet.- Это «место», где будет рендериться содержимое дочернего маршрута.
- Без
Outletвы не увидите дочерние страницы.
Внутренние пути дочерних маршрутов
profileиsettingsзадаются без слеша в начале.- Это относительные пути внутри
/dashboard. - В итоге формируются маршруты
/dashboard/profileи/dashboard/settings.
- Это относительные пути внутри
В навигации внутри
DashboardLayoutиспользуем относительные ссылкиto="profile"иto="settings".- Браузер (точнее, роутер) сам поймет, что нужно построить URL относительно
/dashboard.
- Браузер (точнее, роутер) сам поймет, что нужно построить URL относительно
Относительные и абсолютные пути во вложенных маршрутах
Как формируется полный путь
В React Router (и в большинстве других роутеров) относительный путь дочернего маршрута просто дописывается к пути родителя:
- Родитель: path
/dashboard - Дочерний: path
profile - Итоговый URL:
/dashboard/profile
Если вы случайно укажете путь с ведущим слешем:
<Route path="/profile" element={<ProfilePage />} />
то маршрут станет корневым /profile, а не вложенным в /dashboard. Это распространенная ошибка.
Как правильно использовать относительные ссылки
Смотрите, я покажу вам два варианта ссылок:
// Относительная ссылка - предпочтительна внутри родителя
<Link to="profile">Профиль</Link>
// Абсолютная ссылка - тоже сработает, но привязана к корню
<Link to="/dashboard/profile">Профиль</Link>
Лучше использовать относительные ссылки, если вы находитесь внутри родительского маршрута. Это делает код более гибким: если вы вдруг измените путь родителя с /dashboard на /app, относительные ссылки продолжат работать без правок.
Индексные (index) маршруты внутри вложенных маршрутов
Часто нужно, чтобы при заходе на /dashboard без указания дополнительного сегмента отображалась «главная» страница этого раздела. Например, краткая статистика или приветственный экран.
Для этого используются индексные маршруты.
// Добавим индексный маршрут в наш пример
function DashboardHome() {
return <div>Добро пожаловать в дашборд</div>;
}
export default function App() {
return (
<BrowserRouter>
<Routes>
<Route path="/" element={<HomePage />} />
<Route path="/dashboard" element={<DashboardLayout />}>
{/* Index маршрут - сработает для /dashboard */}
<Route index element={<DashboardHome />} />
{/* Остальные дочерние маршруты */}
<Route path="profile" element={<ProfilePage />} />
<Route path="settings" element={<SettingsPage />} />
</Route>
</Routes>
</BrowserRouter>
);
}
Комментарии по коду:
indexозначает «маршрут по умолчанию» для данного родителя.- Такой маршрут не принимает
path. - При переходе на
/dashboardбудет показан именноDashboardHome.
Это удобнее, чем создавать лишний путь path="" или path="/" и пытаться комбинировать его с родителем.
Вложенные маршруты нескольких уровней
Теперь давайте посмотрим, что будет, если у нас несколько уровней вложенности. Например:
/projects— список проектов./projects/:projectId— детальная страница проекта./projects/:projectId/tasks— задачи проекта./projects/:projectId/tasks/:taskId— конкретная задача.
Структура компонентов
Опишем уровни:
ProjectsLayout— общий layout для/projects.ProjectsListPage— список всех проектов.ProjectLayout— layout для конкретного проекта (шапка, меню).ProjectOverviewPage— обзор проекта.ProjectTasksPage— список задач проекта.TaskDetailsPage— отдельная задача.
Теперь вы увидите, как это можно сконфигурировать.
// ProjectsLayout.jsx
import { Outlet, Link } from "react-router-dom";
export function ProjectsLayout() {
return (
<div>
{/* Общий заголовок раздела Projects */}
<h1>Проекты</h1>
<nav>
<Link to="">Список проектов</Link>
</nav>
{/* Вложенные маршруты /projects/... */}
<Outlet />
</div>
);
}
// ProjectLayout.jsx
import { Outlet, useParams, NavLink } from "react-router-dom";
export function ProjectLayout() {
// Получаем параметр projectId из URL
const { projectId } = useParams();
return (
<div style={{ border: "1px solid #ccc", marginTop: 20, padding: 10 }}>
{/* Шапка конкретного проекта */}
<h2>Проект {projectId}</h2>
{/* Локальное меню конкретного проекта */}
<nav>
<NavLink to="" end>
Обзор
</NavLink>{" "}
|{" "}
<NavLink to="tasks">
Задачи
</NavLink>
</nav>
{/* Вложенные маршруты /projects/:projectId/... */}
<Outlet />
</div>
);
}
Теперь опишем сами маршруты:
// App.jsx (фрагмент с маршрутами проектов)
import { BrowserRouter, Routes, Route } from "react-router-dom";
import { ProjectsLayout } from "./ProjectsLayout";
import { ProjectLayout } from "./ProjectLayout";
function ProjectsListPage() {
return <div>Здесь список всех проектов</div>;
}
function ProjectOverviewPage() {
return <div>Обзор выбранного проекта</div>;
}
function ProjectTasksPage() {
return <div>Список задач проекта</div>;
}
function TaskDetailsPage() {
return <div>Детали конкретной задачи</div>;
}
export default function App() {
return (
<BrowserRouter>
<Routes>
{/* ... другие маршруты */}
<Route path="/projects" element={<ProjectsLayout />}>
{/* /projects */}
<Route index element={<ProjectsListPage />} />
{/* /projects/:projectId */}
<Route path=":projectId" element={<ProjectLayout />}>
{/* /projects/:projectId */}
<Route index element={<ProjectOverviewPage />} />
{/* /projects/:projectId/tasks */}
<Route path="tasks" element={<ProjectTasksPage />} />
{/* /projects/:projectId/tasks/:taskId */}
<Route path="tasks/:taskId" element={<TaskDetailsPage />} />
</Route>
</Route>
</Routes>
</BrowserRouter>
);
}
Здесь используется несколько важных приемов:
- Параметр маршрута
:projectIdобъявлен на уровнеRoute path=":projectId". - Внутри
ProjectLayoutмы читаем его черезuseParams(). - Дочерние маршруты
tasksиtasks/:taskIdавтоматически включаютprojectIdв итоговый путь. - Вы можете продолжать вкладывать маршруты дальше, если это действительно нужно.
Главная идея: каждый уровень маршрута отвечает за свой участок UI и свои данные. Так проще управлять сложным интерфейсом.
Работа с параметрами во вложенных маршрутах
Как читать параметры родителя и ребенка
Если путь такой: /projects/:projectId/tasks/:taskId, то внутри вложенных компонентов вы можете получить оба параметра:
import { useParams } from "react-router-dom";
function TaskDetailsPage() {
const { projectId, taskId } = useParams();
// Здесь вы можете загрузить данные задачи по projectId и taskId
// и отрендерить их
return (
<div>
<h3>Задача {taskId}</h3>
<p>Принадлежит проекту {projectId}</p>
</div>
);
}
Комментарии:
useParamsвозвращает объект со всеми параметрами текущего пути и путей выше.- Если вы находитесь в дочернем маршруте, вы все равно видите параметры, объявленные выше в дереве маршрутов.
Когда параметры лучше поднимать на уровень родителя
Иногда вы загружаете данные проекта в ProjectLayout, а затем дочерние страницы используют эти данные. Это хороший подход:
- Вы не дублируете запросы на каждую дочернюю страницу.
- Передаете данные через контекст или пропсы.
Пример (упрощенный):
// ProjectLayout.jsx
import { Outlet, useParams } from "react-router-dom";
import { useEffect, useState } from "react";
export function ProjectLayout() {
const { projectId } = useParams();
const [project, setProject] = useState(null);
useEffect(() => {
// Здесь имитируем загрузку данных проекта
// В реальном приложении будет запрос к API
setProject({ id: projectId, name: "Demo project" });
}, [projectId]);
if (!project) {
return <div>Загрузка проекта...</div>;
}
return (
<ProjectContext.Provider value={project}>
<h2>{project.name}</h2>
{/* Далее обычный layout + Outlet */}
<Outlet />
</ProjectContext.Provider>
);
}
Так вы используете вложенные маршруты не только для структуры URL, но и для организации логики данных.
Вложенные маршруты и layout-компоненты
Зачем выносить layout на уровень маршрута
Смотрите, я покажу вам типичную проблему. Если не использовать вложенные маршруты, вы можете начать дублировать layout в каждом компоненте страницы:
function ProfilePage() {
return (
<DashboardLayout>
{/* Конкретное содержимое */}
<div>Профиль</div>
</DashboardLayout>
);
}
function SettingsPage() {
return (
<DashboardLayout>
<div>Настройки</div>
</DashboardLayout>
);
}
Проблемы:
- Layout жестко «запаян» в каждую страницу.
- Для смены layout нужно править много файлов.
- Сложно использовать общий стейт и навигацию.
Вложенные маршруты решают это иначе:
// Один общий layout как маршрут
<Route path="/dashboard" element={<DashboardLayout />}>
<Route index element={<DashboardHome />} />
<Route path="profile" element={<ProfilePage />} />
<Route path="settings" element={<SettingsPage />} />
</Route>
Здесь:
DashboardLayoutрендерится всегда, когда URL начинается с/dashboard.- Конкретное содержимое внутри заменяется в
Outlet.
Общие nested-layout уровни
Вы можете выстраивать целую цепочку layout-компонентов:
- Общий layout приложения:
AppLayout(шапка, подвал). - Layout для авторизованных пользователей:
AuthLayout. - Layout для админки:
AdminLayout.
Структура маршрутов будет выглядеть примерно так:
<Route path="/" element={<AppLayout />}>
{/* Публичные страницы */}
<Route index element={<LandingPage />} />
<Route path="login" element={<LoginPage />} />
{/* Защищенный блок */}
<Route element={<AuthLayout />}>
<Route path="profile" element={<ProfilePage />} />
{/* Отдельный layout для админки */}
<Route path="admin" element={<AdminLayout />}>
<Route index element={<AdminHomePage />} />
<Route path="users" element={<AdminUsersPage />} />
<Route path="settings" element={<AdminSettingsPage />} />
</Route>
</Route>
</Route>
Обратите внимание:
- Внутри
AuthLayoutмы можем проверять авторизацию и, если пользователь не авторизован, перенаправлять его на/login. AuthLayoutсам по себе не имеетpath, он просто оборачивает вложенные маршруты.- Это еще один способ воспользоваться nested-routes для построения архитектуры приложения.
Защищенные разделы и nested-routes
Простой ProtectedRoute без вложенности
Обычно защищенный маршрут делают так:
import { Navigate } from "react-router-dom";
function ProtectedRoute({ isAuth, children }) {
// Если пользователь не авторизован - отправляем на логин
if (!isAuth) {
return <Navigate to="/login" replace />;
}
// Иначе рендерим дочерний компонент
return children;
}
Использование:
<Route
path="/profile"
element={
<ProtectedRoute isAuth={isAuth}>
<ProfilePage />
</ProtectedRoute>
}
/>
ProtectedRoute c nested-routes
Теперь посмотрим, как вложенные маршруты делают защиту более гибкой. Например, весь /dashboard должен быть доступен только после авторизации.
// ProtectedLayout.jsx
import { Navigate, Outlet } from "react-router-dom";
export function ProtectedLayout({ isAuth }) {
if (!isAuth) {
// Перенаправляем на страницу логина
return <Navigate to="/login" replace />;
}
// Пользователь авторизован - рендерим вложенные маршруты
return <Outlet />;
}
Использование в дереве маршрутов:
<Route element={<ProtectedLayout isAuth={isAuth} />}>
<Route path="/dashboard" element={<DashboardLayout />}>
<Route index element={<DashboardHome />} />
<Route path="profile" element={<ProfilePage />} />
<Route path="settings" element={<SettingsPage />} />
</Route>
</Route>
Здесь ProtectedLayout — это «прослойка» над dashboard:
- Он не имеет собственного
path. - Он проверяет доступ и рендерит
Outlet. - Все маршруты внутри автоматически становятся защищенными.
Это удобнее, чем добавлять проверку к каждому отдельному маршруту.
Обработка 404 и несовпадающих вложенных маршрутов
Ловим 404 на одном уровне вложенности
Внутри любого Route вы можете добавить маршрут со звездочкой:
<Route path="/dashboard" element={<DashboardLayout />}>
<Route index element={<DashboardHome />} />
<Route path="profile" element={<ProfilePage />} />
<Route path="settings" element={<SettingsPage />} />
{/* Локальный 404 для /dashboard/... */}
<Route path="*" element={<div>Страница дашборда не найдена</div>} />
</Route>
Тогда:
/dashboard/unknownпопадет в этот маршрут./unknownвсе равно будет искать 404 на глобальном уровне.
Глобальный 404
Глобальный 404 обычно располагают в конце списка корневых маршрутов:
<Routes>
<Route path="/" element={<HomePage />} />
<Route path="/dashboard" element={<DashboardLayout />}>
{/* ... вложенные ... */}
</Route>
{/* Глобальный 404 */}
<Route path="*" element={<div>Страница не найдена</div>} />
</Routes>
Важно следить за тем, чтобы звездочка не перехватывала то, что вы планировали обрабатывать на вложенном уровне.
Динамическая подгрузка страниц и код-сплиттинг во вложенных маршрутах
В приложениях с большим количеством nested-routes полезно разбивать код на чанки и подгружать их по мере необходимости.
Пример с React.lazy и Suspense
Давайте посмотрим, как это делается на практике:
import { BrowserRouter, Routes, Route } from "react-router-dom";
import { Suspense, lazy } from "react";
// Динамическая загрузка страниц
const DashboardLayout = lazy(() => import("./DashboardLayout"));
const DashboardHome = lazy(() => import("./DashboardHome"));
const ProfilePage = lazy(() => import("./ProfilePage"));
const SettingsPage = lazy(() => import("./SettingsPage"));
export default function App() {
return (
<BrowserRouter>
{/* Suspense оборачивает маршруты, чтобы показывать fallback при загрузке */}
<Suspense fallback={<div>Загрузка...</div>}>
<Routes>
<Route path="/" element={<HomePage />} />
{/* Layout и дочерние страницы будут подгружены по запросу */}
<Route path="/dashboard" element={<DashboardLayout />}>
<Route index element={<DashboardHome />} />
<Route path="profile" element={<ProfilePage />} />
<Route path="settings" element={<SettingsPage />} />
</Route>
</Routes>
</Suspense>
</BrowserRouter>
);
}
Комментарии:
- Здесь мы не загружаем код для dashboard до тех пор, пока пользователь не зайдет на
/dashboardили один из вложенных маршрутов. - Nested-routes при этом продолжают работать как обычно, просто код компонентов подгружается лениво.
Типичные ошибки и как их избежать
Ошибка 1. Отсутствие Outlet в родительском компоненте
Симптом: вы настроили вложенные маршруты, но при переходе на них отображается только layout, а дочерняя страница не видна.
Причина: в родительском компоненте (DashboardLayout, ProjectLayout и т. д.) забыли добавить Outlet.
Решение:
import { Outlet } from "react-router-dom";
function DashboardLayout() {
return (
<div>
{/* ... общий layout ... */}
<Outlet /> {/* Обязательно */}
</div>
);
}
Ошибка 2. Использование абсолютного пути вместо относительного
Симптом: дочерний маршрут не является частью родительского и открывается по корневому пути.
Причина: указали path="/profile" вместо path="profile".
Решение: убедитесь, что у вложенных маршрутов нет ведущего слеша.
<Route path="/dashboard" element={<DashboardLayout />}>
{/* Правильно */}
<Route path="profile" element={<ProfilePage />} />
{/* Неправильно - это уже корень */}
{/* <Route path="/profile" element={<ProfilePage />} /> */}
</Route>
Ошибка 3. Смешивание index маршрутов и пустого path
Симптом: нестабильное поведение маршрутов по умолчанию внутри родителя.
Причина: пытаются использовать path="" вместо index.
Решение: для маршрута по умолчанию всегда используйте index.
<Route path="/dashboard" element={<DashboardLayout />}>
<Route index element={<DashboardHome />} /> {/* Правильно */}
</Route>
Ошибка 4. Слишком глубокая вложенность без необходимости
Симптом: многоуровневое дерево маршрутов, в котором сложно ориентироваться, хотя UI не так уж глубок.
Причина: каждая мелкая часть интерфейса вынесена в отдельный route.
Решение:
- Делайте nested-routes по уровню логики и по уровню URL.
- Мелкие UI-компоненты лучше оставлять как обычные компоненты, а не отдельные маршруты.
Концептуальные преимущества nested-routes
Чтобы зафиксировать в голове общую картину, давайте кратко перечислим, что вы выигрываете, применяя вложенные маршруты:
Структурированный URL
- Отражает иерархию сущностей в приложении.
- Позволяет пользователям ориентироваться в навигации.
Переиспользуемые layout-компоненты
- Один layout на группу страниц.
- Легко менять общие части интерфейса.
Локализованное состояние
- Данные, загруженные в родителе, переиспользуются дочерними маршрутами.
- Не нужно тянуть все в глобальный стейт без необходимости.
Чистый код маршрутизации
- Дерево маршрутов выглядит как дерево страниц.
- Меньше дублирования и условий.
Гибкая работа с правами доступа
- Один защищающий layout над группой маршрутов.
- Разделение логики авторизации по зонам приложения.
Заключение
Вложенные маршруты — это не просто удобная возможность роутера, а способ спроектировать архитектуру SPA‑приложения так, чтобы навигация, UI и данные были связаны в понятную иерархию.
Если суммировать основные практические моменты:
- Родительский маршрут определяет общий layout и область для дочерних маршрутов через
Outlet. - Дочерние маршруты задаются относительными путями и образуют читаемую структуру URL.
- Параметры, объявленные выше, доступны во всех вложенных маршрутах, а данные, загруженные на уровне layout, легко передавать дальше.
- Index маршруты помогают настроить поведение по умолчанию для разделов.
- Nested-routes хорошо сочетаются с защищенными layout-компонентами и динамической подгрузкой кода.
Далее вы можете углубляться в продвинутые сценарии: раздельные роутеры для микрофронтендов, синхронизация вложенных маршрутов с состоянием вкладок, вложенные маршруты в модальных окнах и другие паттерны. Но базовые принципы всегда остаются теми же — каждый уровень маршрута отвечает за свой уровень интерфейса и логики.
Частозадаваемые технические вопросы по теме статьи и ответы на них
Как во вложенных маршрутах открыть модальное окно по URL а не обычную страницу
Можно сделать отдельный маршрут под модальное состояние и рендерить его поверх предыдущей страницы. Общая идея такая:
- При навигации на маршрут с модальным окном сохраняйте «фон» в состоянии роутера
state: { background: location }. - В корневом компоненте смотрите
location.state?.backgroundи:- рендерите основной
Routesпо background - поверх него рендерите отдельный
Routesтолько с модальными маршрутами.
- рендерите основной
Так модальное окно будет иметь свой URL, но базовая страница останется видимой под ним.
Как правильно использовать nested-routes с HashRouter
С точки зрения nested-routes разница только в том, что перед путями в адресной строке стоит #. Структура Routes, Route, Outlet и относительные пути остаются такими же. Важно:
- Не использовать абсолютные ссылки с полным URL, если вы планируете перенос между BrowserRouter и HashRouter.
- Стараться везде использовать относительные ссылки, чтобы не зависеть от типа роутера.
Как сделать так чтобы часть вложенных маршрутов кешировалась а часть нет
Один из практичных подходов — оборачивать нужные дочерние маршруты в компонент, который сам управляет кешированием. Например:
- Создать
KeepAliveLayout, который:- хранит отрендеренный дочерний компонент в
useRefили контексте; - при повторном заходе возвращает закешированный результат.
- хранит отрендеренный дочерний компонент в
- В маршрутах обернуть нужную группу:
Route element={<KeepAliveLayout />}><Route path="..." ... />.
Альтернатива — библиотечные решения для React (например, компоненты для кеширования маршрутов), которые применяются аналогично.
Как реализовать разный layout для одной и той же страницы в зависимости от пути
Можно объявить один и тот же компонент как element в разных маршрутах с разными родительскими layout:
/dashboard/reports→ родительDashboardLayout./admin/reports→ родительAdminLayout.
В обоих случаях дочерний маршрут может использовать один компонент ReportsPage. Логика страницы остается одной, но окружение (layout) меняется в зависимости от дерева маршрутов.
Как во вложенных маршрутах менять заголовок страницы и хлебные крошки
Обычно делают так:
- На каждом уровне layout добавляют метаданные маршрута (например, через собственную конфигурацию или контекст).
- Родительский layout читает эти метаданные и:
- формирует цепочку хлебных крошек на основе текущего пути и вложенных маршрутов;
- устанавливает
document.titleвuseEffect.
Если вы используете конфигурационный подход (массив объектов с описанием маршрутов), можно хранить заголовок прямо в конфиге и строить breadcrumbs, проходясь по дереву маршрутов и текущему location.pathname.
Постройте личный план изучения Vue до уровня Middle — бесплатно!
Vue — часть карты развития Frontend
100+ шагов развития
30 бесплатных лекций
300 бонусных рублей на счет
Бесплатные лекции
Все гайды по Vue
Лучшие курсы по теме

Vue 3 и Pinia
Антон Ларичев
TypeScript с нуля
Антон Ларичев