Урок 05

Вложенные layouts

Layouts создают иерархию обёрток. Применяются ко всем страницам внутри папки.

Иерархия

app/
├── layout.tsx               ← root
├── page.tsx
├── dashboard/
│   ├── layout.tsx           ← dashboard
│   ├── page.tsx
│   ├── settings/
│   │   └── page.tsx
│   └── profile/
│       └── page.tsx
└── blog/
    ├── layout.tsx           ← blog
    └── page.tsx

Страница /dashboard/settings получит:

root layout
└── dashboard layout
    └── settings page

Root layout

Обязателен. Оборачивает всё.

// app/layout.tsx
export default function RootLayout({ children }: { children: React.ReactNode }) {
    return (
        <html lang="ru">
            <body>{children}</body>
        </html>
    );
}

Должен содержать <html> и <body>.

Вложенный layout

// app/dashboard/layout.tsx
export default function DashboardLayout({ children }: { children: React.ReactNode }) {
    return (
        <div className="dashboard">
            <Sidebar />
            <main>{children}</main>
        </div>
    );
}

Sidebar общий для всех страниц /dashboard/*.

Не перерисовывается

При переходе между /dashboard/settings и /dashboard/profile:

  • Root layout сохраняется
  • Dashboard layout сохраняется
  • Меняется только page

Это быстрее и сохраняет состояние (скролл сайдбара, открытые меню).

Группы маршрутов

Разные layouts для разных секций:

app/
├── layout.tsx
├── (marketing)/
│   ├── layout.tsx           ← маркетинговый layout
│   ├── about/page.tsx       → /about
│   └── pricing/page.tsx     → /pricing
└── (app)/
    ├── layout.tsx           ← layout приложения
    ├── dashboard/page.tsx   → /dashboard
    └── settings/page.tsx    → /settings

/about получит маркетинговый layout, /dashboard — layout приложения.

Получение params в layout

Layout не получает params дочерних страниц (в отличие от страниц). Но получает params своих динамических сегментов:

// app/blog/[category]/layout.tsx
export default async function Layout({
    children,
    params,
}: {
    children: React.ReactNode;
    params: Promise<{ category: string }>;
}) {
    const { category } = await params;
    return (
        <div>
            <h2>Категория: {category}</h2>
            {children}
        </div>
    );
}

Общая навигация

// app/dashboard/layout.tsx
import { NavLink } from '@/components/NavLink';

export default function DashboardLayout({ children }) {
    return (
        <>
            <nav>
                <NavLink href="/dashboard">Обзор</NavLink>
                <NavLink href="/dashboard/settings">Настройки</NavLink>
                <NavLink href="/dashboard/profile">Профиль</NavLink>
            </nav>
            <main>{children}</main>
        </>
    );
}

template.tsx

Как layout, но пересоздаётся на каждом переходе:

// app/dashboard/template.tsx
'use client';

export default function Template({ children }: { children: React.ReactNode }) {
    return <div className="animate-in">{children}</div>;
}

Используется для анимаций, useEffect при смене страницы.

Modal через параллельные маршруты

Несколько layouts одновременно:

app/
├── layout.tsx
└── @modal/
    └── default.tsx
└── posts/
    └── [id]/
        └── page.tsx

Сложная тема, но позволяет показывать модалку поверх страницы без потери URL.

Итоги

  • Layouts вкладываются по цепочке
  • Root layout обязателен
  • Не перерисовываются при переходах
  • Группы (name) для разных layouts
  • Layout получает params своих сегментов
  • template.tsx пересоздаётся