1. Углубляемся в layout.tsx
Если вы когда-нибудь пытались реализовать “шапку” и “подвал” (header и footer), которые видны на всех страницах сайта, вы наверняка сталкивались с проблемой: как не копировать один и тот же код в каждую страницу? В классическом React (или в Page Router Next.js) это обычно делается через компонент-обёртку, но в App Router появился свой, более мощный и удобный способ — layout-система.
Layout — это компонент, который определяет “скелет” для группы страниц. Он автоматически применяется ко всем страницам и дочерним layouts, находящимся в той же папке или ниже по структуре.
template — это специальный компонент, похожий на layout, но с другим поведением: он создаёт новый экземпляр при каждой навигации, а layout — сохраняет один и тот же.
Давайте разберёмся подробнее!
Где и как создаётся layout.tsx?
В любой папке внутри app/ вы можете (и должны!) создать файл layout.tsx. Это файл, который определяет, как будут выглядеть все вложенные страницы и layouts внутри этой папки.
app/
layout.tsx // главный layout для всего приложения
page.tsx // главная страница ("/")
about/
layout.tsx // layout только для /about и его детей
page.tsx // страница "/about"
team/
page.tsx // страница "/about/team"
Пояснение:
app/layout.tsx — применяется ко всему приложению.
app/about/layout.tsx — применяется только к /about и всем его потомкам (например, /about/team).
Пример простого layout.tsx
// app/layout.tsx
export default function RootLayout({ children }) {
return (
<html lang="ru">
<body>
<header>Мой сайт</header>
<main>{children}</main>
<footer>© 2024</footer>
</body>
</html>
);
}
Пояснение:
children — это всё содержимое, которое вложено в этот layout (страницы, другие layouts, templates).
Layout должен возвращать валидную структуру HTML. Обычно это <html>, <body>, header/footer и место для children.
Особенности layout.tsx
- Layout автоматически применяется ко всем вложенным страницам.
- Layout кэшируется: при переходе между страницами, которые используют один и тот же layout, его компонент не пересоздаётся (не сбрасывается state, не пересоздаются эффекты).
- Можно создавать layout на любом уровне вложенности: для всего приложения, для секций, для отдельных групп маршрутов.
Как работает вложенность layout'ов?
Layout'ы можно вкладывать друг в друга. Например, если у вас есть layout для всего сайта и отдельный layout для секции “about”, то структура рендеринга будет примерно такой:
RootLayout
└── AboutLayout
└── AboutPage (или TeamPage и т.д.)
Каждый layout получает в props свой children — это всё, что ниже по иерархии.
2. template.tsx: “Свежий” шаблон на каждый переход
Что такое template.tsx?
template.tsx — это ещё один специальный файл App Router, который похож на layout, но с важным отличием: template пересоздаётся при каждом переходе на новую страницу внутри своей области.
Зачем это нужно? Иногда вы хотите, чтобы при переходе между страницами сбрасывался state, перезапускались эффекты, заново запускались анимации и т.д. Layout для этого не подходит, потому что он сохраняет своё состояние между переходами.
Пример использования template.tsx
// app/about/template.tsx
export default function AboutTemplate({ children }) {
return (
<section style={{ border: "2px solid blue", padding: "1rem" }}>
<h2>Секция "О нас"</h2>
{children}
</section>
);
}
Пояснение:
Все страницы и компоненты внутри /about будут обёрнуты в этот шаблон.
Каждый раз, когда вы переходите на новую страницу внутри /about, экземпляр AboutTemplate будет создаваться заново.
Когда нужен template.tsx, а не layout.tsx?
- Если внутри обёртки есть локальный state, который должен сбрасываться при каждом переходе (например, модальные окна, формы, временные данные).
- Если нужны анимации “появления” страницы.
- Если нужно, чтобы компонент пересоздавался при навигации, а не сохранял старое состояние.
3. Сравнение layout.tsx и template.tsx
| Характеристика | layout.tsx | template.tsx |
|---|---|---|
| Жизненный цикл | Один раз при монтировании, сохраняется между переходами | Пересоздаётся при каждом переходе |
| Сброс состояния | State сохраняется | State сбрасывается |
| Использование | Для общего “скелета” (header, sidebar, footer) | Для анимаций, сброса состояния, “шаблонов” страниц |
| Вложенность | Можно вкладывать друг в друга | Можно вкладывать, но обычно используется локально |
| Пример | |
|
Аналогия:
Layout — это как стены и крыша дома: они всегда одни и те же, даже если вы переставляете мебель.
Template — это как декорации на вечеринке: каждый раз, когда приходит новая компания гостей, вы всё снимаете и заново украшаете комнату.
4. Как layout и template работают вместе: схема рендеринга
Представим структуру:
app/
layout.tsx
about/
layout.tsx
template.tsx
page.tsx
team/
page.tsx
При переходе с /about на /about/team:
- Root layout (app/layout.tsx) — не пересоздаётся.
- About layout (about/layout.tsx) — не пересоздаётся.
- About template (about/template.tsx) — пересоздаётся!
- Страница — пересоздаётся, естественно.
app/layout.tsx
└─ about/layout.tsx
└─ about/template.tsx (пересоздаётся при каждом переходе)
└─ page.tsx или team/page.tsx
5. Пример: layout.tsx и template.tsx в действии
layout.tsx с навигацией и футером
// app/layout.tsx
export default function RootLayout({ children }) {
return (
<html lang="ru">
<body>
<nav>
<a href="/">Главная</a> | <a href="/about">О нас</a>
</nav>
<hr />
{children}
<footer>© 2024 Все права защищены</footer>
</body>
</html>
);
}
about/layout.tsx: layout для секции
// app/about/layout.tsx
export default function AboutLayout({ children }) {
return (
<div style={{ background: "#e0f7fa", padding: "1rem" }}>
<h1>О нас</h1>
{children}
</div>
);
}
about/template.tsx: сброс состояния
// app/about/template.tsx
import { useState } from "react";
export default function AboutTemplate({ children }) {
const [count, setCount] = useState(0);
return (
<div>
<p>Локальный счетчик: {count}</p>
<button onClick={() => setCount(count + 1)}>+1</button>
<div>{children}</div>
</div>
);
}
Пояснение:
При переходе между /about и /about/team счётчик будет сбрасываться на 0, потому что template пересоздаётся.
Layout (и root layout, и about layout) — не сбрасывают state, даже если он там есть.
6. Типичные ошибки при работе с layout.tsx и template.tsx
Ошибка №1: Ожидание сброса состояния в layout, а не в template.
Многие новички думают, что если поместить useState в layout, то он будет сбрасываться при переходах между страницами. На самом деле, layout сохраняет своё состояние, и сброса не произойдёт. Если вам нужно сбрасывать state — используйте template.tsx.
Ошибка №2: Отсутствие обязательной структуры в layout.tsx.
Главный layout (app/layout.tsx) должен возвращать <html> и <body>. Если этого не сделать, приложение может вести себя странно (например, некорректно работать с метаданными, стилями и т.д.).
Ошибка №3: Дублирование кода между layout и template.
Иногда разработчики копируют один и тот же код и в layout, и в template. Лучше разделять: layout — для “скелета”, template — для сбрасываемых обёрток.
Ошибка №4: Хранение глобального состояния в template.
Если вы поместите, например, Redux Provider или Context Provider в template, состояние будет сбрасываться при каждом переходе между страницами. Обычно глобальные провайдеры должны быть в layout.
Ошибка №5: Неочевидная вложенность layouts и templates.
Иногда сложно понять, какой layout или template применяется к какой странице. Лайфхак: смотрите на структуру папок — layout и template применяются ко всем файлам внутри своей папки и вложенных папок, пока не встретится новый layout/template.
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ