К содержанию
Первый Коммит

Модуль 1

Введение и основы Next.js

  • Что такое Next.js
  • Установка
  • Структура проекта
  • Файловая маршрутизация

#1.1. Что такое Next.js и зачем он нужен

Next.js — это React-фреймворк для создания полнофункциональных веб-приложений. Он предоставляет инструменты и конфигурацию, необходимые для React, и добавляет собственные возможности: серверный рендеринг, статическую генерацию, файловую маршрутизацию, оптимизацию изображений и многое другое. На момент написания курса актуальная версия — Next.js 16, в её составе React 19.2.

#Почему не просто React (CRA / Vite)?

Чистый React (с Vite или Create React App) — это SPA (Single Page Application). Всё рендерится на клиенте. Это создаёт проблемы:

  • SEO — поисковые роботы видят пустой HTML
  • Первая загрузка — пользователь ждёт, пока скачается весь JS-бандл
  • Нет серверной логики — нужен отдельный бэкенд
  • Маршрутизация — нужно устанавливать react-router вручную

Next.js решает все эти проблемы из коробки:

  • SSR/SSG — HTML генерируется на сервере, SEO работает
  • Быстрая загрузка — пользователь сразу видит контент
  • Fullstack — серверная логика в самом проекте (Server Actions, API Routes)
  • Файловая маршрутизация — создание файла = создание страницы
  • Оптимизации — изображения, шрифты, код-сплиттинг автоматически

#Сравнительная таблица

ВозможностьCRA/ViteNext.js (Pages)Next.js (App)
Серверный рендеринг❌✅ getServerSideProps✅ По умолчанию
Статическая генерация❌✅ getStaticProps✅ По умолчанию
Файловая маршрутизация❌✅ pages/✅ app/
Server Components❌❌✅
Server Actions❌❌✅
Streaming / Suspense❌Частично✅

#1.2. Установка и создание проекта

#Предварительные требования

  • Node.js 24 LTS (сам Next.js 16 запускается и на 20.9, но демо курса рассчитаны на 24)
  • npm, yarn или pnpm (в курсе используется npm)
  • Редактор кода (рекомендуется VS Code)
  • Windows: скачайте установщик с https://nodejs.org (LTS-кнопка). После установки перезапустите терминал.
  • macOS: либо тот же установщик, либо Homebrew: brew install node@24 (нужен Homebrew — brew.sh). Рекомендуется nvm: brew install nvm, затем nvm install --lts.
  • Ubuntu/Linux: рекомендуется nvm. Команды: curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/master/install.sh | bash → перезапустите терминал → nvm install --lts.

nvm удобен тем, что позволяет переключаться между версиями Node — это пригодится, когда у разных проектов разные требования.

Проверьте установку Node.js:

Терминал
node --version
# Должно быть v24 или выше

npm --version

#Как открыть терминал на вашей ОС

Перед практикой убедитесь, что умеете открывать терминал и переходить в нужную папку. Это базовый навык — без него дальнейшая работа невозможна.

  • Windows (рекомендуется PowerShell): нажмите Win → начните вводить «PowerShell» → откройте «Windows PowerShell». Альтернатива — Win+R, ввести cmd, Enter (классический cmd.exe).
  • Windows Terminal (новое приложение от Microsoft): Microsoft Store → найти «Windows Terminal». Он совмещает PowerShell, cmd и WSL в одной программе с вкладками — рекомендуется установить.
  • macOS: Cmd+Space → начните вводить «Terminal» → Enter. Или Finder → Программы → Утилиты → Терминал.
  • Ubuntu/Linux: Ctrl+Alt+T (стандартное сочетание). Альтернатива — найти «Terminal» в меню приложений.

Чтобы перейти в нужную папку, используйте команду cd (от англ. «change directory»):

Терминал
# Windows (PowerShell или cmd): обратные слеши работают, но прямые тоже
cd C:\Users\Username\Projects
cd C:/Users/Username/Projects   # тоже валидно

# macOS / Linux: только прямые слеши
cd ~/Projects                   # ~ означает домашнюю папку
cd /Users/username/Projects     # macOS
cd /home/username/Projects      # Linux

Проверьте, в какой папке вы сейчас находитесь:

Код
# Windows (PowerShell)
pwd

# Windows (cmd)
cd

# macOS / Linux
pwd

#1.3. Практика: создаём проект

Шаг 1. Откройте терминал и выполните команду:

Терминал
npx create-next-app@16.3.0 my-first-nextjs-app

Шаг 2. Ответьте на вопросы установщика:

Код
✔ What is your project named? → my-first-nextjs-app
✔ Would you like to use the recommended Next.js defaults? → No, customize settings
✔ Would you like to use TypeScript? → Yes
✔ Which linter would you like to use? → ESLint
✔ Would you like to use React Compiler? → No
✔ Would you like to use Tailwind CSS? → Yes
✔ Would you like your code inside a `src/` directory? → No
✔ Would you like to use App Router? (recommended) → Yes
✔ Would you like to customize the import alias (`@/*` by default)? → No
✔ Would you like to include AGENTS.md to guide coding agents? → Yes
  • TypeScript: безопаснее JavaScript, выявляет ошибки на этапе написания кода. В индустрии для Next.js это стандарт.
  • ESLint: проверяет код на типичные ошибки. Biome — современная альтернатива (быстрее), но в курсе используется ESLint, поскольку большинство примеров в интернете рассчитано на него.
  • React Compiler: автоматически оптимизирует ре-рендеры. Стабилен в Next.js 16, но на этом этапе отключается — так проще осваивать базовые концепции. Будет включён позже, в модуле про оптимизацию.
  • Tailwind CSS: utility-first CSS-фреймворк. В Next.js 16 поставляется Tailwind v4 — конфигурация теперь задаётся в CSS-файле, а не в tailwind.config.ts.
  • Директория src/: без неё проще — папка app/ находится сразу в корне.
  • App Router: единственный современный путь. Pages Router в курсе не используется.
  • Алиас @/*: позволяет писать import Foo from "@/components/Foo" вместо длинного относительного пути.
  • AGENTS.md: файл с подсказками для AI-инструментов (Copilot, Cursor, Claude). К работе Next.js не относится — при желании его можно удалить.

Шаг 3. Перейдите в папку проекта и запустите:

Терминал
cd my-first-nextjs-app
npm run dev

Шаг 4. Откройте браузер:

Перейдите по адресу http://localhost:3000. Откроется стартовая страница Next.js с логотипом и ссылками на документацию.

#Если что-то пошло не так (типовые ошибки)

  • Проверьте подключение к интернету (особенно в колледже — там бывают проблемы с прокси/сертификатами, см. страницу «Перед началом работы»).
  • Прервите процесс (Ctrl+C), удалите созданную папку, запустите команду заново.
  • Если проблема в SSL — выполните: npm config set strict-ssl false (только для учебных целей!).
  • Значит, npm пытается писать в системную папку без прав. Лучшее решение — использовать nvm (см. блок выше): он устанавливает Node в домашнюю папку.
  • Никогда не запускайте npm с sudo — это в дальнейшем портит права доступа к файлам.
  • Команда запускается не из папки проекта. Проверьте: cd my-first-nextjs-app, затем снова npm run dev.
  • Проверьте, что сервер в терминале всё ещё запущен и слушает порт (должна быть строка «Local: http://localhost:3000»).
  • Если порт 3000 занят — Next.js выведет сообщение «Port 3000 is in use, trying 3001 instead». Откройте указанный в терминале адрес.
  • Брандмауэр Windows при первом запуске может запросить разрешение — предоставьте его для частной сети.
  • Вероятно, зависимости установлены не полностью. Запустите npm install в папке проекта.

#1.4. Структура проекта

После создания проекта структура файлов выглядит так:

Код
my-first-nextjs-app/
├── app/                    # Главная папка приложения (App Router)
│   ├── favicon.ico         # Иконка сайта
│   ├── globals.css         # Глобальные стили
│   ├── layout.tsx          # Корневой layout (обёртка для всех страниц)
│   └── page.tsx            # Главная страница (/)
├── public/                 # Статические файлы (картинки, шрифты)
│   ├── file.svg
│   ├── globe.svg
│   ├── next.svg
│   ├── vercel.svg
│   └── window.svg
├── AGENTS.md               # Подсказки AI-инструментам (можно удалить)
├── CLAUDE.md               # То же для Claude Code (можно удалить)
├── README.md               # Описание проекта
├── eslint.config.mjs       # Конфигурация ESLint (flat config)
├── .gitignore
├── next.config.ts          # Конфигурация Next.js (TypeScript)
├── package.json            # Зависимости и скрипты
├── postcss.config.mjs      # Конфигурация PostCSS (для Tailwind v4)
└── tsconfig.json           # Конфигурация TypeScript

#Ключевые файлы и папки

app/ — ключевая папка приложения. Здесь находятся все страницы, layouts и компоненты.

public/ — статические файлы. Доступны по URL напрямую: public/logo.png → http://localhost:3000/logo.png

next.config.ts — конфигурация Next.js (редиректы, env-переменные, плагины).

package.json — скрипты: dev, build, start, lint

#Скрипты в package.json

JSON
{
  "scripts": {
    "dev": "next dev",               // Запуск dev-сервера (Turbopack теперь по умолчанию)
    "build": "next build",           // Сборка для продакшена
    "start": "next start",           // Запуск продакшен-сервера
    "lint": "eslint"                // Проверка кода ESLint (next lint в v16 удалён)
  }
}

Разница между dev и build + start:

  • npm run dev — режим разработки, горячая перезагрузка, подробные сообщения об ошибках
  • npm run build — создаёт оптимизированную сборку в папке .next/
  • npm run start — запускает собранное приложение (как на сервере)

#1.5. App Router vs Pages Router

В Next.js существуют два подхода к маршрутизации:

Pages Router (старый)App Router (новый)
Папкаpages/app/
КомпонентыТолько клиентскиеСерверные по умолчанию
Data fetchinggetServerSideProps и др.async-компоненты + fetch
LayoutsРучные (_app.tsx)Вложенные layout.tsx
Loading/ErrorРучныеloading.tsx, error.tsx
Server Actions❌✅

#1.6. Файловая маршрутизация

В Next.js App Router каждая папка внутри app/ — это сегмент URL. Файл page.tsx внутри папки делает этот сегмент доступным как страницу.

#Базовые маршруты

Код
app/
├── page.tsx              → /
├── about/
│   └── page.tsx          → /about
├── contact/
│   └── page.tsx          → /contact
└── blog/
    └── page.tsx          → /blog

#Специальные файлы

Код
app/
├── layout.tsx      # Обёртка (сохраняется при навигации)
├── page.tsx        # Уникальный контент страницы
├── loading.tsx     # UI загрузки (автоматический Suspense)
├── error.tsx       # Обработка ошибок
├── not-found.tsx   # Страница 404
└── template.tsx    # Как layout, но пересоздаётся при навигации

layout.tsx — оборачивает дочерние страницы. Не перерисовывается при навигации между дочерними маршрутами. Применяется для шапки, сайдбара, футера.

page.tsx — содержимое конкретной страницы. Без этого файла маршрут не будет доступен.

loading.tsx — показывается автоматически, пока page.tsx загружает данные.

error.tsx — перехватывает ошибки в дочерних компонентах (Error Boundary).

not-found.tsx — показывается при вызове notFound() или при отсутствии маршрута.

#1.7. Практика: создаём страницы

Шаг 1. Замените содержимое файла app/page.tsx:

JavaScript
// app/page.tsx
export default function HomePage() {
  return (
    <div>
      <h1>Главная страница</h1>
      <p>Добро пожаловать в мой первый Next.js проект!</p>
    </div>
  );
}

Шаг 2. Создайте папку app/about/ и файл page.tsx:

JavaScript
// app/about/page.tsx
export default function AboutPage() {
  return (
    <div>
      <h1>О нас</h1>
      <p>Это страница «О нас». URL: /about</p>
    </div>
  );
}

Шаг 3. Создайте папку app/contact/ и файл page.tsx:

JavaScript
// app/contact/page.tsx
export default function ContactPage() {
  return (
    <div>
      <h1>Контакты</h1>
      <p>Напишите нам: hello@example.com</p>
    </div>
  );
}

Шаг 4. Проверьте в браузере:

  • http://localhost:3000 → Главная страница
  • http://localhost:3000/about → О нас
  • http://localhost:3000/contact → Контакты
  • http://localhost:3000/anything → 404 (страница не найдена)

#1.8. Вложенные маршруты и layout.tsx

#Вложенные маршруты

Код
app/
└── blog/
    ├── page.tsx              → /blog
    └── [slug]/
        └── page.tsx          → /blog/my-first-post

Практика: блог с вложенными маршрутами

Шаг 1. Создайте app/blog/page.tsx:

JavaScript
// app/blog/page.tsx
import Link from "next/link";

export default function BlogPage() {
  const posts = [
    { slug: "first-post", title: "Первый пост" },
    { slug: "second-post", title: "Второй пост" },
    { slug: "nextjs-intro", title: "Введение в Next.js" },
  ];

  return (
    <div>
      <h1>Блог</h1>
      <ul>
        {posts.map((post) => (
          <li key={post.slug}>
            <Link href={`/blog/${post.slug}`}>{post.title}</Link>
          </li>
        ))}
      </ul>
    </div>
  );
}

Шаг 2. Создайте app/blog/[slug]/page.tsx:

JavaScript
// app/blog/[slug]/page.tsx
export default async function BlogPostPage({
  params,
}: {
  params: Promise<{ slug: string }>;
}) {
  const { slug } = await params;

  return (
    <div>
      <h1>Пост: {slug}</h1>
      <p>Это динамическая страница для поста с slug: {slug}</p>
    </div>
  );
}

Шаг 3. Проверьте в браузере:

  • http://localhost:3000/blog → Список постов
  • http://localhost:3000/blog/first-post → Пост: first-post
  • http://localhost:3000/blog/anything → Пост: anything

#Layout — общая обёртка

layout.tsx оборачивает все дочерние страницы. Корневой layout (app/layout.tsx) обязателен и содержит <html> и <body>.

Шаг 4. Отредактируйте app/layout.tsx:

JavaScript
// app/layout.tsx
import type { Metadata } from "next";
import Link from "next/link";
import "./globals.css";

export const metadata: Metadata = {
  title: "Мой Next.js сайт",
  description: "Учебный проект",
};

export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <html lang="ru">
      <body>
        <nav style={{ padding: "1rem", borderBottom: "1px solid #ccc" }}>
          <Link href="/" style={{ marginRight: "1rem" }}>Главная</Link>
          <Link href="/about" style={{ marginRight: "1rem" }}>О нас</Link>
          <Link href="/blog" style={{ marginRight: "1rem" }}>Блог</Link>
          <Link href="/contact">Контакты</Link>
        </nav>
        <main style={{ padding: "2rem" }}>{children}</main>
      </body>
    </html>
  );
}

Шаг 5. Проверьте в браузере:

Теперь на каждой странице есть навигация сверху. При переходе между страницами навигация не перерисовывается — за такое поведение отвечает layout.

#Вложенный layout для блога

Шаг 6. Создайте app/blog/layout.tsx:

JavaScript
// app/blog/layout.tsx
export default function BlogLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <div style={{ display: "flex", gap: "2rem" }}>
      <aside style={{
        width: "200px",
        borderRight: "1px solid #ccc",
        paddingRight: "1rem",
      }}>
        <h3>Категории</h3>
        <ul>
          <li>JavaScript</li>
          <li>React</li>
          <li>Next.js</li>
        </ul>
      </aside>
      <div style={{ flex: 1 }}>{children}</div>
    </div>
  );
}

Шаг 7. Проверьте:

Перейдите на /blog — слева появится сайдбар с категориями. Перейдите на /blog/first-post — сайдбар остаётся. Перейдите на /about — сайдбара нет, потому что он определён только для /blog/*.

#1.9. Группы маршрутов и динамические сегменты

#Группы маршрутов (Route Groups)

Папки в круглых скобках (group) не влияют на URL, но позволяют организовать код:

Код
app/
├── (marketing)/
│   ├── about/
│   │   └── page.tsx      → /about (НЕ /marketing/about!)
│   └── pricing/
│       └── page.tsx      → /pricing
├── (dashboard)/
│   ├── layout.tsx        # Отдельный layout для дашборда
│   ├── settings/
│   │   └── page.tsx      → /settings
│   └── profile/
│       └── page.tsx      → /profile
└── layout.tsx

Это полезно, когда у разных разделов сайта разные layouts (например, у маркетинговых страниц и дашборда).

#Динамические сегменты

Код
// Один сегмент
app/blog/[slug]/page.tsx        → /blog/my-post
// params: { slug: "my-post" }

// Несколько сегментов
app/shop/[category]/[id]/page.tsx  → /shop/electronics/42
// params: { category: "electronics", id: "42" }

// Catch-all (все оставшиеся сегменты)
app/docs/[...slug]/page.tsx     → /docs/a/b/c
// params: { slug: ["a", "b", "c"] }

// Optional catch-all
app/docs/[[...slug]]/page.tsx   → /docs  ИЛИ  /docs/a/b/c
// params: { slug: undefined } ИЛИ { slug: ["a", "b", "c"] }

#1.10. Практика: магазин с категориями

Шаг 1. Создайте app/shop/page.tsx:

JavaScript
// app/shop/page.tsx
import Link from "next/link";

export default function ShopPage() {
  const categories = ["electronics", "clothing", "books"];

  return (
    <div>
      <h1>Магазин</h1>
      <ul>
        {categories.map((cat) => (
          <li key={cat}>
            <Link href={`/shop/${cat}`}>{cat}</Link>
          </li>
        ))}
      </ul>
    </div>
  );
}

Шаг 2. Создайте app/shop/[category]/page.tsx:

JavaScript
// app/shop/[category]/page.tsx
import Link from "next/link";

export default async function CategoryPage({
  params,
}: {
  params: Promise<{ category: string }>;
}) {
  const { category } = await params;
  const products = [
    { id: "1", name: `${category} товар 1` },
    { id: "2", name: `${category} товар 2` },
    { id: "3", name: `${category} товар 3` },
  ];

  return (
    <div>
      <h1>Категория: {category}</h1>
      <ul>
        {products.map((product) => (
          <li key={product.id}>
            <Link href={`/shop/${category}/${product.id}`}>
              {product.name}
            </Link>
          </li>
        ))}
      </ul>
    </div>
  );
}

Шаг 3. Создайте app/shop/[category]/[id]/page.tsx:

JavaScript
// app/shop/[category]/[id]/page.tsx
export default async function ProductPage({
  params,
}: {
  params: Promise<{ category: string; id: string }>;
}) {
  const { category, id } = await params;

  return (
    <div>
      <h1>Товар #{id}</h1>
      <p>Категория: {category}</p>
      <p>Здесь будет подробная информация о товаре.</p>
    </div>
  );
}

Шаг 4. Проверьте:

  • http://localhost:3000/shop → Список категорий
  • http://localhost:3000/shop/electronics → Товары в категории
  • http://localhost:3000/shop/electronics/1 → Страница товара

#1.11. loading.tsx, error.tsx, not-found.tsx

#loading.tsx — индикатор загрузки

Шаг 1. Создайте app/blog/loading.tsx:

JavaScript
// app/blog/loading.tsx
export default function Loading() {
  return (
    <div style={{ padding: "2rem", textAlign: "center" }}>
      <p>⏳ Загрузка постов...</p>
    </div>
  );
}

Шаг 2. Добавьте искусственную задержку в app/blog/page.tsx:

JavaScript
// app/blog/page.tsx
import Link from "next/link";

async function getPosts() {
  await new Promise((resolve) => setTimeout(resolve, 2000));
  return [
    { slug: "first-post", title: "Первый пост" },
    { slug: "second-post", title: "Второй пост" },
  ];
}

export default async function BlogPage() {
  const posts = await getPosts();

  return (
    <div>
      <h1>Блог</h1>
      <ul>
        {posts.map((post) => (
          <li key={post.slug}>
            <Link href={`/blog/${post.slug}`}>{post.title}</Link>
          </li>
        ))}
      </ul>
    </div>
  );
}

Шаг 3. Проверьте:

Перейдите на /blog — на 2 секунды появится «⏳ Загрузка постов...», затем отобразится список. Next.js автоматически оборачивает page.tsx в <Suspense> с fallback из loading.tsx.

#error.tsx — обработка ошибок

Шаг 4. Создайте app/blog/error.tsx:

JavaScript
// app/blog/error.tsx
"use client"; // error.tsx ОБЯЗАТЕЛЬНО должен быть клиентским!

export default function Error({
  error,
  retry,
}: {
  error: Error & { digest?: string };
  retry: () => void;
}) {
  return (
    <div style={{ padding: "2rem", color: "red" }}>
      <h2>Ошибка!</h2>
      <p>{error.message}</p>
      <button onClick={() => retry()}>Попробовать снова</button>
    </div>
  );
}

Функцию retry Next.js передаёт в error.tsx сам: она заново запрашивает у сервера упавший сегмент и перерисовывает его. Если причина ошибки была разовой (не ответил внешний сервис, база перезапускалась), повтор покажет страницу. Если ошибка в коде, повтор снова закончится ошибкой, и блок останется на месте.

Шаг 5. Проверьте работу error.tsx:

Для этого временно вызовите ошибку на странице блога. В файле app/blog/page.tsx добавьте выброс ошибки первой строкой функции getPosts:

Код
throw new Error("Тестовая ошибка загрузки постов");

Обновите http://localhost:3000/blog — вместо списка постов появится красный блок «Ошибка!» с текстом ошибки и кнопкой «Попробовать снова». Значит, сработал Error Boundary из error.tsx: он перехватил ошибку дочерней страницы и показал запасной интерфейс. Убедившись, что блок работает, удалите строку с throw — иначе страница блога так и будет завершаться ошибкой.

#not-found.tsx — страница 404

Шаг 6. Создайте app/not-found.tsx:

JavaScript
// app/not-found.tsx
import Link from "next/link";

export default function NotFound() {
  return (
    <div style={{ padding: "2rem", textAlign: "center" }}>
      <h1>404 — Страница не найдена</h1>
      <p>Запрашиваемая страница не существует.</p>
      <Link href="/">Вернуться на главную</Link>
    </div>
  );
}

Шаг 7. Проверьте:

Перейдите на http://localhost:3000/nonexistent — откроется созданная вами страница 404.

#Итоги модуля 1

  • Next.js — React-фреймворк с SSR, SSG, файловой маршрутизацией
  • App Router — современный подход (папка app/)
  • page.tsx — создаёт страницу, layout.tsx — общая обёртка
  • Динамические сегменты: [slug], [...slug], [[...slug]]
  • Группы маршрутов: (group) — не влияют на URL
  • loading.tsx, error.tsx, not-found.tsx — автоматическая обработка состояний

#Домашнее задание

Все задания выполняются в одном новом проекте — сайте-афише городских мероприятий; данные берутся из обычного массива в коде, работа с сервером и базой появится в следующих модулях.

  • Создайте проект командой create-next-app с теми же ответами установщика, что и в практике 1.3, и соберите каркас афиши: корневой layout.tsx с шапкой-навигацией и подвалом, главную страницу с приветствием и тремя ближайшими событиями и страницу /events со списком всех событий; сам массив событий (идентификатор, название, дата, площадка, описание) вынесите в файл lib/events.ts и импортируйте его на страницах.
  • Добавьте разделу событий собственную обёртку и карточки: файл app/events/layout.tsx с заголовком раздела и боковой колонкой фильтров-заглушек, а внутри — динамический маршрут app/events/[id]/page.tsx, который находит событие по идентификатору из params (в Next.js 16 params — это Promise, его распаковывают через await) и выводит подробную карточку.
  • Оформите служебные страницы через группу маршрутов: папка (info) со страницами help и rules — адреса должны остаться /help и /rules, без сегмента info; положите в группу собственный layout.tsx с навигацией по служебным страницам и убедитесь, что на страницах событий он не появляется.
  • Добавьте разделу событий состояния загрузки и ошибки: loading.tsx со скелетоном списка (чтобы его увидеть, вставьте в страницу искусственную задержку await new Promise((resolve) => setTimeout(resolve, 1500))) и error.tsx с текстом ошибки и кнопкой повтора, вызывающей retry; для проверки error.tsx временно бросьте исключение в странице события. Файл loading.tsx положите так, чтобы он накрывал только список, а не весь раздел: если он окажется прямо в app/events, то будет действовать и на страницу отдельного события, а тогда следующий пункт задания перестанет работать — ответ начинает уходить браузеру до вызова notFound(), и вместо кода 404 несуществующее событие вернёт 200. Простое решение — завести для списка группу маршрутов, например app/events/(list), и держать page.tsx и loading.tsx внутри неё: адрес /events от этого не изменится.
  • Закройте несуществующие адреса: собственная страница app/not-found.tsx со ссылкой на главную, а в app/events/[id]/page.tsx — вызов notFound() из next/navigation, когда события с таким идентификатором в массиве нет.
  • (со звёздочкой) Добавьте раздел справки со свободной вложенностью на необязательном catch-all сегменте app/docs/[[...slug]]/page.tsx: по адресу /docs открывается оглавление, по /docs/tickets — статья, по /docs/tickets/refund — вложенная статья, а для неизвестного пути вызывается notFound(); хлебные крошки соберите из массива сегментов, а сам раздел оберните отдельным вложенным layout.tsx с боковым меню.

Войдите, чтобы отмечать пройденные модули и видеть прогресс по курсу.

Введите запрос: название модуля, тема или раздел.

Стрелки — выбор, Enter — открыть. Ищет по названиям модулей, темам и разделам.