JavaRush /Курсы /Модуль 4: Node.js, Next.js и Angular /Вложенные маршруты и страницы, структура вложенности в Ne...

Вложенные маршруты и страницы, структура вложенности в Next.js 15 (App Router)

Модуль 4: Node.js, Next.js и Angular
8 уровень , 7 лекция
Открыта

1. Введение

В реальных приложениях почти всегда есть разделы и подразделы. Например, у вас есть страница пользователя /users, а внутри — детальная страница пользователя /users/123, а ещё внутри — вкладки "Профиль", "Настройки", "История заказов" и так далее.

Вложенные маршруты позволяют:

  • Организовать код и структуру приложения логично и понятно;
  • Переиспользовать общий интерфейс (например, боковое меню или шапку) для целых разделов;
  • Создавать вложенные layouts для разных уровней вложенности;
  • Делать навигацию и загрузку данных более эффективной.

В Next.js 15 с App Router это реализуется максимально прозрачно — через структуру папок и файлов.

Принцип file-based routing

Давайте начнем с простой идеи — в Next всё строится по принципу: что в папке — то и на маршруте.
Каждая папка внутри app/ — это сегмент маршрута, а файл page.tsx внутри папки — это страница, которая рендерится по этому маршруту.


app/
  users/
    page.tsx           // /users
    [id]/
      page.tsx         // /users/123
      settings/
        page.tsx       // /users/123/settings

Пояснение:

  • app/users/page.tsx — страница /users
  • app/users/[id]/page.tsx — страница /users/123
  • app/users/[id]/settings/page.tsx — страница /users/123/settings

Важно!
Каждый уровень вложенности — это новая папка.
Динамические сегменты ([id]) работают на любом уровне.

2. Вложенные layouts: как это работает?

Layout — "скелет" для вложенных страниц

layout.tsx — это компонент, который определяет общий шаблон для всех страниц внутри данной папки.


app/
  dashboard/
    layout.tsx         // Общий layout для dashboard
    page.tsx           // /dashboard
    analytics/
      page.tsx         // /dashboard/analytics
      settings/
        page.tsx       // /dashboard/analytics/settings

Пояснение:

  • app/dashboard/layout.tsx — применяется ко всем страницам /dashboard/*
  • app/dashboard/analytics/page.tsx — вложенная страница, получает layout dashboard

Пример кода layout:


// app/dashboard/layout.tsx
export default function DashboardLayout({ children }) {
  return (
    <div>
      <aside>Меню Dashboard</aside>
      <main>{children}</main>
    </div>
  );
}

children — это всё, что лежит "ниже" по маршруту (страницы, вложенные layouts и т.д.).

Вложенность layouts

Layouts могут быть вложенными:

  • Если у вас есть app/layout.tsx (корневой layout), он применяется ко всему приложению;
  • Если есть app/dashboard/layout.tsx, он применяется ко всем страницам внутри /dashboard/*, поверх корневого layout;
  • Если добавить app/dashboard/analytics/layout.tsx, он будет применяться только к /dashboard/analytics/*.

app/layout.tsx
  └─ app/dashboard/layout.tsx
       └─ app/dashboard/analytics/layout.tsx
            └─ app/dashboard/analytics/page.tsx

3. Пример: строим вложенную структуру для учебного приложения

Допустим, мы делаем учебное приложение с разделами "Курсы", "Профиль" и "Настройки". Внутри "Курсы" есть список курсов и страница отдельного курса с вкладками "Описание" и "Материалы".


app/
  layout.tsx                 // общий layout для всего приложения
  courses/
    layout.tsx               // layout для раздела "Курсы"
    page.tsx                 // /courses — список курсов
    [courseId]/
      layout.tsx             // layout для отдельного курса
      page.tsx               // /courses/123 — страница курса
      description/
        page.tsx             // /courses/123/description
      materials/
        page.tsx             // /courses/123/materials
  profile/
    page.tsx                 // /profile
  settings/
    page.tsx                 // /settings

Что это даёт?

  • На всех страницах будет шапка и футер из app/layout.tsx.
  • В разделе "Курсы" (/courses/*) появится боковое меню из app/courses/layout.tsx.
  • Для каждой страницы курса (/courses/123/*) можно добавить, например, меню вкладок через app/courses/[courseId]/layout.tsx.

4. Примеры: layouts и вложенные страницы

Корневой layout


// app/layout.tsx
export default function RootLayout({ children }) {
  return (
    <html>
      <body>
        <header>Моё учебное приложение</header>
        {children}
        <footer>© 2024</footer>
      </body>
    </html>
  );
}

Layout для раздела "Курсы"


// app/courses/layout.tsx
export default function CoursesLayout({ children }) {
  return (
    <div style={{ display: "flex" }}>
      <nav>
        <ul>
          <li><a href="/courses">Все курсы</a></li>
          <li><a href="/courses/new">Создать курс</a></li>
        </ul>
      </nav>
      <main style={{ marginLeft: 20 }}>{children}</main>
    </div>
  );
}

Layout для отдельного курса (например, вкладки)


// app/courses/[courseId]/layout.tsx
export default function CourseLayout({ children, params }) {
  return (
    <div>
      <h2>Курс #{params.courseId}</h2>
      <nav>
        <a href={`/courses/${params.courseId}/description`}>Описание</a> |{" "}
        <a href={`/courses/${params.courseId}/materials`}>Материалы</a>
      </nav>
      <section>{children}</section>
    </div>
  );
}

Вложенные страницы


// app/courses/[courseId]/description/page.tsx
export default function DescriptionPage({ params }) {
  return <div>Описание курса #{params.courseId}</div>;
}

// app/courses/[courseId]/materials/page.tsx
export default function MaterialsPage({ params }) {
  return <div>Материалы курса #{params.courseId}</div>;
}

5. Полезные нюансы

Как работает рендеринг вложенных страниц

Когда пользователь заходит на /courses/123/materials, Next.js собирает страницу по следующей "матрёшке":

  1. app/layout.tsx — общий layout (шапка, футер)
  2. app/courses/layout.tsx — layout раздела "Курсы" (боковое меню)
  3. app/courses/[courseId]/layout.tsx — layout для конкретного курса (меню вкладок)
  4. app/courses/[courseId]/materials/page.tsx — содержимое страницы "Материалы"

Каждый layout "оборачивает" следующий уровень через пропс children.
Это похоже на слоёный пирог: каждый уровень добавляет свою начинку.

Схема вложенности: наглядно


/courses/123/materials

app/layout.tsx
  └─ app/courses/layout.tsx
       └─ app/courses/[courseId]/layout.tsx
            └─ app/courses/[courseId]/materials/page.tsx

Пояснение:

  • layout.tsx: общий для всего приложения
  • courses/layout.tsx: для всех страниц в разделе "Курсы"
  • [courseId]/layout.tsx: для всех страниц конкретного курса
  • materials/page.tsx: конкретная страница ("Материалы")

Практические советы и нюансы

  • Можно ли пропустить layout?
    Да! Layout — опционален. Если в папке нет layout, будет использован layout из родительской папки.
  • Как делать вложенные динамические сегменты?
    Можно! Например, /users/[userId]/posts/[postId]/page.tsx — это страница поста пользователя.
  • Как добавить "группы маршрутов"?
    Если хотите сгруппировать папки, не влияя на URL, используйте круглые скобки:
    app/(dashboard)/analytics/page.tsx — это будет /analytics, но в коде можно логически разделять части приложения.
  • Как работают параметры?
    В каждом layout и странице вы получаете объект params с нужными значениями.
    Например, в app/courses/[courseId]/materials/page.tsx
    params.courseId — это ID курса.
  • Как делать вложенную навигацию?
    Используйте <Link href="..."> из next/link для переходов без перезагрузки страницы.

6. Типичные ошибки при работе с вложенными маршрутами

Ошибка №1: неправильное расположение файлов layout/page
Иногда разработчики кладут layout.tsx на уровень, где он не нужен, или забывают создать его там, где нужен отдельный layout. В итоге либо дублируется код, либо layout применяется не к тем страницам.

Ошибка №2: забыли вложить папку для вложенного маршрута
Если вы хотите сделать маршрут /users/123/settings, но кладёте settings/page.tsx прямо в /users/, получится /users/settings, а не /users/123/settings. Для вложенности всегда нужны новые папки!

Ошибка №3: дублирование кода в layouts
Вместо того чтобы переиспользовать layouts, некоторые копируют одинаковый код в разные layouts. Лучше выносить общие части в компоненты и импортировать их.

Ошибка №4: неправильная работа с params
В layout'ах и страницах параметры берутся из объекта params. Если вы ошиблись в имени (например, написали params.id вместо params.courseId), будет ошибка или пустое значение.

Ошибка №5: попытка использовать layout как страницу
Layout не должен содержать основное содержимое страницы, а только оболочку для вложенных children. Не пишите логику страницы в layout!

Ошибка №6: забыли про children
Если в layout не рендерить {children}, вложенные страницы не появятся вообще — будет пусто. Это классика для новичков.

Комментарии
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ