JavaRush /Курсы /Модуль 4: Node.js, Next.js и Angular /Поведение при навигации: layout vs template (Next.js 15, ...

Поведение при навигации: layout vs template (Next.js 15, App Router)

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

1. Введение

Если вы уже делали хоть что-то на Next.js 15 с App Router (а если нет — самое время начать!), то наверняка замечали: иногда при переходе по страницам часть интерфейса "мигает", иногда — остаётся на месте. Иногда состояние сбрасывается, иногда — сохраняется.

Всё это связано с тем, как Next.js "переиспользует" или "пересоздаёт" layout и template-компоненты при навигации. Понимание этих нюансов позволяет:

  • Избегать "мигания" или потери состояния на страницах.
  • Грамотно размещать логику и состояние (например, общие меню, корзину, авторизацию).
  • Достигать максимальной производительности и плавности навигации.

Если коротко: layout — это про "стабильность", template — про "гибкость". Но давайте разберёмся подробнее!

Напоминаем: что такое layout.tsx и template.tsx?

layout.tsx — это компонент-обёртка для группы маршрутов. Он определяет общую структуру (например, шапку, меню, подвал) для всех вложенных страниц и маршрутов. Layout'ы могут быть вложенными: каждый вложенный layout добавляет свою "обёртку".

template.tsx — это тоже обёртка, но с другим поведением: она создаётся заново при каждом посещении маршрута, к которому относится. Используется для случаев, когда нужно динамически изменять структуру обёртки или сбрасывать состояние при переходах.

Пример структуры


app/
  layout.tsx         // Главный layout для всего приложения
  page.tsx           // Главная страница
  about/
    page.tsx         // Страница "О нас"
  dashboard/
    layout.tsx      // Layout для dashboard
    template.tsx     // Template для dashboard
    page.tsx         // Главная страница dashboard
    analytics/
      page.tsx       // Вложенная страница analytics

2. Как layout и template ведут себя при навигации

Layout: живёт долго и счастливо

Layout-компоненты в Next.js 15 устроены так, что они сохраняют своё состояние между переходами по вложенным маршрутам. Это значит:

  • Если пользователь переходит между страницами, находящимися "под" одним и тем же layout, этот layout не пересоздаётся.
  • Любое состояние (например, открытое меню, выбранная вкладка) внутри layout сохраняется, пока пользователь не выйдет из этой области маршрутов.

Иллюстрация


app/
  layout.tsx
  dashboard/
    layout.tsx
    page.tsx
    analytics/
      page.tsx
  • Переход с /dashboard на /dashboard/analytics — оба маршрута используют один и тот же layout (dashboard/layout.tsx), он не пересоздаётся.
  • Переход с /dashboard/analytics на /about — верхний layout (app/layout.tsx) остаётся, но layout dashboard "уходит".

Почему это круто?

  • Не теряется состояние, например, открытые вкладки, фильтры, содержимое корзины и т.д.
  • Нет лишних перерисовок — быстрее и плавнее для пользователя.

Template: каждый раз как новый

Template-компоненты ведут себя иначе: они пересоздаются при каждом переходе на новый маршрут в своей области. Это значит:

  • Если вы переходите между маршрутами, для которых используется один и тот же template, он будет пересоздан (и его состояние сброшено).
  • Это удобно для сброса временного состояния, например, если вы хотите, чтобы при каждом заходе на страницу фильтры или формы были "чистыми".

Иллюстрация


app/
  dashboard/
    template.tsx
    page.tsx
    analytics/
      page.tsx

Переход с /dashboard на /dashboard/analytics — template пересоздаётся, всё, что было внутри template, сбрасывается.

3. Примеры: как это выглядит на практике

Пример 1: Layout сохраняет состояние


// app/dashboard/layout.tsx
'use client';
import { useState } from "react";

export default function DashboardLayout({ children }) {
  const [menuOpen, setMenuOpen] = useState(false);

  return (
    <div>
      <button onClick={() => setMenuOpen(open => !open)}>
        {menuOpen ? "Скрыть меню" : "Показать меню"}
      </button>
      {menuOpen && <nav>Меню</nav>}
      <main>{children}</main>
    </div>
  );
}

Пояснение:
— Откройте /dashboard, нажмите "Показать меню".
— Перейдите на /dashboard/analytics (например, через <Link href="/dashboard/analytics">).
— Меню останется открытым! Layout не пересоздавался.

Пример 2: Template сбрасывает состояние


// app/dashboard/template.tsx
'use client';
import { useState } from "react";

export default function DashboardTemplate({ children }) {
  const [counter, setCounter] = useState(0);

  return (
    <div>
      <p>Счётчик: {counter}</p>
      <button onClick={() => setCounter(c => c + 1)}>+1</button>
      {children}
    </div>
  );
}

Пояснение:
— Находясь на /dashboard, нажмите "+1" несколько раз.
— Перейдите на /dashboard/analytics.
— Счётчик снова 0! Template пересоздался, состояние сброшено.

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

Когда использовать layout, а когда template?

Layout — для стабильных обёрток и общего состояния

  • Вам нужно, чтобы часть интерфейса (меню, шапка, сайдбар) оставалась на месте при навигации по вложенным страницам.
  • Вы хотите сохранять состояние между переходами (например, открытые вкладки, фильтры, корзина).
  • Требуется общая структура для группы маршрутов.

Template — для сброса состояния и динамических обёрток

  • Нужно, чтобы при каждом переходе на страницу состояние сбрасывалось (например, сбросить форму, фильтры).
  • Требуется динамически создавать обёртку для каждой страницы (например, уникальные анимации перехода).
  • Вы не хотите, чтобы пользователь "тащил" старое состояние между переходами.

Как понять, что layout/template пересоздался?

Иногда хочется "пощупать" разницу. Давайте добавим простой console.log:


// app/dashboard/layout.tsx
console.log('Dashboard layout rendered');

// app/dashboard/template.tsx
console.log('Dashboard template rendered');

Пояснение:
— Переключайтесь между /dashboard и /dashboard/analytics и смотрите в консоль браузера.
— Layout будет выводиться только при первом заходе или уходе из dashboard, а template — каждый раз при переходе между страницами dashboard.

Взаимодействие layout и template: кто кого оборачивает?

Если в одной папке есть и layout.tsx, и template.tsx, то структура вложенности будет такой:


layout.tsx
  └─ template.tsx
       └─ page.tsx
  • Layout всегда "выше" (оборачивает) template.
  • Template "заворачивает" только page.tsx (и вложенные маршруты).

Схема


app/
  layout.tsx
    dashboard/
      layout.tsx
        template.tsx
          page.tsx
          analytics/
            page.tsx

Переход между /dashboard и /dashboard/analytics:

  • app/layout.tsx — не пересоздаётся.
  • dashboard/layout.tsx — не пересоздаётся.
  • dashboard/template.tsx — пересоздаётся.
  • page.tsx — пересоздаётся.

Где лучше хранить состояние?

  • Общее состояние (например, авторизация, корзина, глобальные настройки) — в самом верхнем layout (app/layout.tsx).
  • Состояние раздела (например, фильтры dashboard) — в layout соответствующего раздела (dashboard/layout.tsx).
  • Временное состояние страницы (например, значения формы) — внутри page.tsx или template.tsx.

Если вы случайно поместите важное состояние в template, оно будет теряться при навигации — и вы получите "странные баги", когда корзина вдруг опустела или фильтр сбросился.

5. Типичные ошибки при работе с layout и template

Ошибка №1: Сброс состояния из-за использования template вместо layout.
Новички часто помещают состояние (например, фильтры или открытое меню) в template, ожидая, что оно сохранится при переходах по разделу. В результате при каждом переходе состояние сбрасывается, что раздражает пользователей: "Почему мой фильтр постоянно сбрасывается?!". Если хотите сохранить состояние — используйте layout.

Ошибка №2: "Мигание" интерфейса из-за частых пересозданий.
Если вы используете template там, где нужен layout, интерфейс может "мигать" — например, шапка или меню исчезают и появляются заново при каждом переходе. Это снижает плавность и UX.

Ошибка №3: Неожиданное поведение при глубокой вложенности.
Если у вас много уровней layout и template, легко запутаться, какой компонент когда пересоздаётся. Следите за структурой и используйте консоль для отладки.

Ошибка №4: Размещение тяжёлых вычислений или запросов в template.
Поскольку template пересоздаётся часто, тяжёлые операции внутри него будут выполняться каждый раз при переходе, что может замедлить приложение.

Ошибка №5: Попытка хранить глобальное состояние в template.
Template предназначен для "одноразовых" обёрток. Если вам нужно глобальное или разделяемое состояние — используйте layout или глобальные менеджеры состояния (например, React Context).

1
Задача
Модуль 4: Node.js, Next.js и Angular, 8 уровень, 9 лекция
Недоступна
Статические страницы о питомцах
Статические страницы о питомцах
1
Задача
Модуль 4: Node.js, Next.js и Angular, 8 уровень, 9 лекция
Недоступна
Динамическая страница о фильме по id
Динамическая страница о фильме по id
1
Задача
Модуль 4: Node.js, Next.js и Angular, 8 уровень, 9 лекция
Недоступна
Вложенные страницы рецептов с layout
Вложенные страницы рецептов с layout
1
Задача
Модуль 4: Node.js, Next.js и Angular, 8 уровень, 9 лекция
Недоступна
FAQ с вложенными маршрутами и группой
FAQ с вложенными маршрутами и группой
1
Задача
Модуль 4: Node.js, Next.js и Angular, 8 уровень, 9 лекция
Недоступна
Мини-блог с динамическими и параллельными маршрутами
Мини-блог с динамическими и параллельными маршрутами
3
Опрос
Группы маршрутов, 8 уровень, 9 лекция
Недоступен
Группы маршрутов
Группы маршрутов
Комментарии
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ