1. Синтаксис динамических сегментов: [id]
В реальных приложениях редко бывает достаточно только статических маршрутов вроде /about или /contacts. Обычно нужно что-то вроде:
- /users/123
- /blog/react-hooks
- /products/iphone-15
Где 123, react-hooks или iphone-15 — это переменные части адреса, которые определяют, какую именно информацию показывать.
В Next.js такие переменные части называются динамическими сегментами. Они позволяют создавать одну страницу, которая работает с разными параметрами из URL.
Основной синтаксис
Чтобы создать динамический сегмент, нужно назвать папку или файл в папке app/ в квадратных скобках. Например:
app/
users/
[id]/
page.tsx
Пояснение: [id] — это имя параметра. Оно будет доступно в компоненте страницы.
Пример:
- URL /users/42 отобразит компонент из app/users/[id]/page.tsx, где id = "42"
- URL /users/alice — id = "alice"
Пример кода
// app/users/[id]/page.tsx
import React from "react";
type Props = {
params: { id: string }
};
export default function UserPage({ params }: Props) {
return (
<div>
<h1>Профиль пользователя</h1>
<p>ID: {params.id}</p>
</div>
);
}
Пояснение:
- Параметр id берётся из URL и доступен через пропс params.
- Если открыть /users/123, на странице будет написано ID: 123.
Как это работает?
Next.js автоматически сопоставляет любую часть пути на месте [id] и передаёт её значение в компонент страницы.
2. Множественные динамические сегменты
Можно делать вложенные динамические сегменты. Например, для адреса /users/42/posts/99:
app/
users/
[userId]/
posts/
[postId]/
page.tsx
В компоненте будут доступны оба параметра: userId и postId.
// app/users/[userId]/posts/[postId]/page.tsx
export default function PostPage({ params }) {
return (
<div>
<h1>Пост пользователя</h1>
<p>User ID: {params.userId}</p>
<p>Post ID: {params.postId}</p>
</div>
);
}
3. Кастомизация URL: [...slug] — "catch-all" сегменты
Иногда нужно обработать адреса с произвольным количеством частей. Например:
- /docs/intro
- /docs/guides/getting-started
- /docs/guides/advanced/optimizations
Вместо создания кучи вложенных папок можно использовать catch-all сегмент — многоточие в квадратных скобках: [...slug].
app/
docs/
[...slug]/
page.tsx
Такой сегмент "собирает" все части пути после /docs/ в массив params.slug.
Пример
- /docs/intro → params.slug = ["intro"]
- /docs/guides/getting-started → params.slug = ["guides", "getting-started"]
- /docs/guides/advanced/optimizations → params.slug = ["guides", "advanced", "optimizations"]
// app/docs/[...slug]/page.tsx
import React from "react";
type Props = {
params: { slug?: string[] }
};
export default function DocsPage({ params }: Props) {
return (
<div>
<h1>Документация</h1>
<p>Путь: {params.slug ? params.slug.join(" / ") : "Главная документации"}</p>
</div>
);
}
Если открыть /docs/guides/advanced/optimizations, увидите: Путь: guides / advanced / optimizations
Важно!
Если пользователь зайдёт просто на /docs, то params.slug будет undefined. Это не ошибка — просто массив пустой.
4. Опциональные catch-all сегменты: [[...slug]]
Что делать, если вы хотите, чтобы страница работала и для /docs (без дополнительных частей), и для /docs/что-угодно? Для этого есть опциональный catch-all сегмент: двойные квадратные скобки.
app/
docs/
[[...slug]]/
page.tsx
Пояснение:
- Теперь и /docs и /docs/anything/else будут попадать в этот маршрут.
- Если путь — просто /docs, то params.slug будет undefined или пустым массивом.
Пример:
// app/docs/[[...slug]]/page.tsx
export default function DocsPage({ params }) {
const slug = params.slug ?? [];
return (
<div>
<h1>Документация</h1>
<p>
{slug.length === 0
? "Главная документации"
: "Путь: " + slug.join(" / ")}
</p>
</div>
);
}
5. Полезные нюансы
Сравнение: [id], [...slug], [[...slug]]
| Синтаксис | Что делает? | Пример URL | Значение params |
|---|---|---|---|
|
Одна часть пути (обязательная) | |
|
|
Одна или больше частей (catch-all, обязательно хотя бы 1 часть) | |
|
|
Любое количество частей (в том числе ни одной, то есть опционально) |
|
или |
Как использовать параметры в компоненте
В Next.js App Router параметры из динамических сегментов попадают в проп params вашего компонента страницы. Обычно типизация выглядит так:
type Props = {
params: { [key: string]: string | string[] | undefined }
}
Пояснение:
- Для [id] — это строка.
- Для [...slug] или [[...slug]] — это массив строк (или undefined).
6. Практика: динамические страницы в вашем учебном приложении
Давайте добавим к вашему учебному приложению страницу просмотра отдельной задачи по её id.
Структура:
app/
tasks/
[id]/
page.tsx
Код:
// app/tasks/[id]/page.tsx
import React from "react";
// Имитация базы задач
const tasks = [
{ id: "1", title: "Изучить Next.js", done: false },
{ id: "2", title: "Сделать домашку", done: true },
];
export default function TaskPage({ params }) {
const task = tasks.find((t) => t.id === params.id);
if (!task) {
return <div>Задача не найдена</div>;
}
return (
<div>
<h1>Задача: {task.title}</h1>
<p>Статус: {task.done ? "Выполнено" : "В процессе"}</p>
</div>
);
}
Пояснение:
- Откройте /tasks/1 — увидите первую задачу.
- Откройте /tasks/999 — получите сообщение "Задача не найдена".
7. Типичные ошибки и нюансы
Ошибка №1: Папка названа без скобок
Если вы назовёте папку просто id вместо [id], динамическая маршрутизация работать не будет. Next.js будет считать это обычным статическим маршрутом.
Ошибка №2: Несоответствие типов
Для [id] параметр всегда строка, для [...slug] — массив строк. Если не учитывать это, можно получить ошибку при попытке вызвать .join() на строке или обратиться к индексу массива, когда его нет.
Ошибка №3: Необработанные значения params
Если не проверить, что params.slug существует и является массивом (для catch-all), можно получить ошибку при попытке использовать .join() на undefined. Всегда делайте проверку или используйте оператор ?? [].
Ошибка №4: Конфликт путей
Если у вас есть и статическая страница (/docs/page.tsx) и catch-all (/docs/[...slug]/page.tsx), Next.js может выбрать не тот маршрут. Следите за уникальностью путей.
Ошибка №5: Путаница между [...slug] и [[...slug]]
Если вы хотите, чтобы маршрут работал и без дополнительных сегментов (например, /docs), используйте двойные скобки — [[...slug]]. Обычные [...slug] не сработают для пустого пути.
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ