Маршруты и общая структура страниц
Связываем файлы с адресами и сохраняем общую оболочку страниц.
На этой странице
Для Next.js 16.3.8 (App Router) и React 19.3.0. Обновлено .
Маршрут связывает адрес с содержимым страницы. В App Router папка задает часть адреса, файл page.tsx – страницу. Обычный файл компонента сам по себе не создает адрес.
Файлы и адреса
afisha/
├── app/
│ ├── layout.tsx → общая оболочка
│ ├── page.tsx → /
│ └── events/
│ ├── page.tsx → /events
│ └── [id]/
│ └── page.tsx → /events/1, /events/2, ...
└── lib/
└── events.ts → данные, не страница[id] – динамическая часть маршрута. Одна реализация страницы обслуживает разные идентификаторы. В браузере нужно открыть /events/1, а не адрес с буквальными скобками.
Общие данные
Создайте папку lib и файл events.ts. Эта модель понадобится следующим статьям:
export type EventItem = { id: number; name: string; price: number };
export const events: EventItem[] = [
{ id: 1, name: "Кинопоказ", price: 300 },
{ id: 2, name: "Лекция", price: 0 },
];
export function getEvent(id: string) {
return events.find((event) => String(event.id) === id);
}Параметр id из адреса приходит как строка. Мы сравниваем её со строковой записью идентификатора, поэтому "1" находит первое мероприятие, а "unknown" не находит ни одного. find возвращает объект или undefined.
Если на главной остался фильтр из Next-01, удалите из app/page.tsx его локальный const events и добавьте import { events } from "@/lib/events". Теперь главная, каталог и отдельные мероприятия используют один источник. Изменение цены в lib/events.ts отразится во всех этих местах.
Страница каталога и Link
Создайте app/events/page.tsx:
import Link from "next/link";
import { events } from "@/lib/events";
export default function EventsPage() {
return (
<main>
<h1>Афиша</h1>
<ul>
{events.map((event) => (
<li key={event.id}>
<Link href={`/events/${event.id}`}>{event.name}</Link>
</li>
))}
</ul>
</main>
);
}@/ – алиас, то есть сокращенный путь, настроенный установщиком. В нашем проекте он указывает на корневую папку. Link поддерживает переходы Next.js без полной перезагрузки документа. Для внешнего сайта обычно достаточно обычной ссылки a.
Получаем параметр страницы
Создайте файл в папке [id]:
import { notFound } from "next/navigation";
import { getEvent } from "@/lib/events";
export default async function EventPage({
params,
}: {
params: Promise<{ id: string }>;
}) {
const { id } = await params;
const event = getEvent(id);
if (!event) notFound();
return (
<main>
<h1>{event.name}</h1>
<p>{event.price} ₽</p>
</main>
);
}В Next.js 16 params – Promise: сначала дождитесь его через await. Тип после двоеточия описывает ожидаемый объект параметров. notFound() завершает показ этой страницы как несуществующей; свой экран для неё добавим в статье о состояниях.
Общая оболочка
Замените app/layout.tsx, сохранив созданные страницы:
import type { ReactNode } from "react";
import Link from "next/link";
import "./globals.css";
export default function RootLayout({ children }: { children: ReactNode }) {
return (
<html lang="ru">
<body>
<nav aria-label="Основная навигация">
<Link href="/">Главная</Link> <Link href="/events">Афиша</Link>
</nav>
{children}
</body>
</html>
);
}В children Next.js передает содержимое текущей страницы. Корневой layout содержит html и body. Вложенный app/events/layout.tsx мог бы добавить общую оболочку только каталогу и его дочерним страницам; второй html внутри него не нужен.
Общий layout сохраняется при переходах между его страницами. Если нужно выделить текущий пункт меню, адрес можно получить в клиентском компоненте через usePathname.
Попробуйте: откройте /events/1 напрямую, перейдите к лекции и откройте /events/99. Проверьте, что общая навигация не скопирована в каждую страницу.
Далее – сервер и браузер. Документация: страницы и общая структура, Link.