1. Зачем нужны специальные файлы в App Router?
Если вы раньше работали с обычным React-приложением, то наверняка привыкли к тому, что структура папок — это просто структура папок, а не магия. В Next.js 15 с App Router всё по-другому: структура папок и имена файлов напрямую управляют маршрутизацией и поведением приложения. Это похоже на то, как если бы ваша файловая система была одновременно и картой сайта, и схемой рендеринга.
В каждой папке внутри app/ вы можете (а иногда и должны) создать специальные файлы:
- page.tsx — отвечает за содержимое конкретной страницы.
- layout.tsx — определяет общий "скелет" (например, шапка, подвал, меню) для всех вложенных страниц.
- template.tsx — позволяет создавать новые "экземпляры" layout-а для каждой навигации (например, если нужно сбрасывать состояние).
Давайте разберём каждый из них подробно и с примерами.
2. page.tsx — сердце страницы
Что это такое?
Файл page.tsx — это точка входа для маршрута (route), то есть для отдельной страницы вашего сайта. Если в папке есть page.tsx, значит по этому пути будет доступна страница.
app/
└── about/
└── page.tsx
Синтаксис
// app/about/page.tsx
export default function AboutPage() {
return <h1>О нас</h1>;
}
Особенности
- В каждом сегменте маршрута может быть только один page.tsx.
- Именно этот файл отвечает за то, что будет показано пользователю на конкретной странице.
- В отличие от классических маршрутизаторов, вам не нужно вручную настраивать роуты — Next.js сам найдёт все page.tsx и построит маршруты.
Пример с параметрами
Если вы хотите сделать динамическую страницу (например, для профиля пользователя), используйте квадратные скобки:
app/
└── user/
└── [id]/
└── page.tsx
Теперь страница будет доступна по адресу /user/123, /user/vasya и т.д.
// app/user/[id]/page.tsx
import { useParams } from 'next/navigation';
export default function UserPage() {
// В Server Component параметры приходят через props, а не через useParams!
// Но для простоты — пример с клиентским компонентом:
const params = useParams();
return <div>Профиль пользователя с id: {params.id}</div>;
}
3. layout.tsx — общий каркас страницы
Что это такое?
Файл layout.tsx — это не просто обёртка, а настоящий "скелет" для всех вложенных страниц и маршрутов. Всё, что вы поместите в layout, будет оборачивать все дочерние страницы и даже другие layout-ы, если они есть глубже.
Это похоже на то, как если бы вы строили многоэтажный дом: у каждого этажа свои стены, но фундамент и несущие конструкции (layout) общие для всех.
Синтаксис
// app/layout.tsx
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="ru">
<body>
<header>Это шапка сайта</header>
<main>{children}</main>
<footer>Это подвал</footer>
</body>
</html>
);
}
Важно: layout-ы могут быть вложенными! Например, в app/blog/layout.tsx можно сделать отдельное меню только для блога.
Особенности
- Layout-ы "живут" на всём уровне вложенности. Если вы переходите между страницами внутри одного layout-а, он не размонтируется (сохраняет состояние).
- Если вы хотите, чтобы layout сбрасывался при переходе — вам нужен template.tsx (см. ниже).
- Layout может быть серверным или клиентским компонентом. По умолчанию — серверный.
Пример вложенных layout-ов
app/
├── layout.tsx // Корневой layout для всего приложения
├── blog/
│ ├── layout.tsx // Layout только для блога
│ └── page.tsx // Страница блога
└── about/
└── page.tsx // Страница "О нас"
В этом примере страница /blog будет обёрнута сначала в корневой layout, потом в blog-layout, а страница /about — только в корневой.
4. template.tsx — новый экземпляр layout-а при каждом переходе
Что это такое?
template.tsx — это специальный файл, который внешне очень похож на layout, но с одним важным отличием: он создаёт новый экземпляр при каждом переходе по маршруту.
Если layout — это как декорации в театре, которые не меняются между сценами, то template — как новые декорации для каждой новой сцены.
Когда использовать?
Используйте template.tsx, если:
- Нужно, чтобы состояние сбрасывалось при каждом переходе на новую страницу.
- Например, если у вас есть форма с локальным состоянием и вы хотите, чтобы при переходе на другую страницу эта форма сбрасывалась (layout не сбросит состояние, а template — да).
Синтаксис
// app/blog/[postId]/template.tsx
export default function BlogPostTemplate({ children }: { children: React.ReactNode }) {
return (
<section>
<aside>Меню поста</aside>
<article>{children}</article>
</section>
);
}
Пример отличия layout и template
Допустим, вы сделали layout с каким-то состоянием (например, счётчиком):
// app/counter/layout.tsx
'use client'
import { useState } from 'react';
export default function CounterLayout({ children }) {
const [count, setCount] = useState(0);
return (
<div>
<button onClick={() => setCount(count + 1)}>+1</button>
<div>Текущее значение: {count}</div>
{children}
</div>
);
}
Если вы переходите между разными страницами внутри /counter/, значение счётчика не сбросится (layout не размонтируется).
Если же вы реализуете это через template.tsx, то при каждом переходе значение сбросится — будет новый экземпляр компонента.
5. Полезные нюансы
Сравнительная таблица: layout.tsx vs page.tsx vs template.tsx
| Файл | Назначение | Особенности |
|---|---|---|
|
Содержимое конкретной страницы | Только один на папку, формирует конечный маршрут |
|
Общий каркас для всех вложенных страниц | Сохраняет состояние между переходами, может быть вложенным |
|
Новый экземпляр layout-а для каждой навигации | Сбрасывает состояние при каждом переходе, похож на layout по синтаксису |
Как всё это работает вместе: схема
Вот небольшая схема, как Next.js строит дерево компонентов при переходе по маршруту /blog/42:
app/
├── layout.tsx
├── blog/
│ ├── layout.tsx
│ ├── [postId]/
│ │ ├── template.tsx
│ │ └── page.tsx
Порядок обёртывания:
- app/layout.tsx — корневой layout
- app/blog/layout.tsx — layout для блога
- app/blog/[postId]/template.tsx — template для конкретного поста
- app/blog/[postId]/page.tsx — содержимое страницы поста
6. Практический пример: создаём структуру мини-блога
Давайте на практике создадим структуру для мини-блога с главной страницей, списком постов и отдельной страницей поста.
app/
├── layout.tsx
├── page.tsx
├── blog/
│ ├── layout.tsx
│ ├── page.tsx
│ └── [postId]/
│ ├── template.tsx
│ └── page.tsx
- app/layout.tsx — общий header/footer для всего сайта.
- app/page.tsx — главная страница.
- app/blog/layout.tsx — меню блога (например, фильтры, поиск).
- app/blog/page.tsx — список всех постов.
- app/blog/[postId]/template.tsx — сбрасывает состояние при открытии нового поста.
- app/blog/[postId]/page.tsx — страница отдельного поста.
Пример кода для app/layout.tsx:
export default function RootLayout({ children }) {
return (
<html lang="ru">
<body>
<header>Блог компании</header>
{children}
<footer>© 2024 Все права защищены</footer>
</body>
</html>
);
}
Пример кода для app/blog/layout.tsx:
export default function BlogLayout({ children }) {
return (
<section>
<nav>Меню блога: <a href="/blog">Все посты</a></nav>
<div>{children}</div>
</section>
);
}
Пример для app/blog/[postId]/template.tsx:
'use client'
import { useState } from 'react';
export default function BlogPostTemplate({ children }) {
// Состояние сбрасывается при каждом переходе на новый пост!
const [comment, setComment] = useState('');
return (
<div>
<input
value={comment}
onChange={e => setComment(e.target.value)}
placeholder="Оставьте комментарий"
/>
{children}
</div>
);
}
8. Типичные ошибки при работе с layout.tsx, page.tsx и template.tsx
Ошибка №1: Пытаетесь сделать несколько page.tsx в одной папке.
Next.js не позволит — в каждом сегменте маршрута может быть только один файл page.tsx. Если хочется несколько страниц — делайте вложенные папки.
Ошибка №2: Не добавили проп children в layout/template.
Layout и template обязаны принимать проп children — иначе ничего не будет отображаться внутри!
Ошибка №3: Ожидаете, что состояние layout сбросится при переходе.
Layout сохраняет своё состояние между переходами по вложенным страницам. Если нужно сбрасывать — используйте template.tsx.
Ошибка №4: Используете хуки React в layout без 'use client'.
По умолчанию layout — серверный компонент. Если вы хотите использовать хуки (useState, useEffect), не забудьте добавить в начало файла строку 'use client'.
Ошибка №5: Путаете назначение файлов.
page.tsx — только страница, layout.tsx — только обёртка, template.tsx — только для сброса состояния. Не пытайтесь "запихнуть" всё в один файл.
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ