Динамические маршруты
Сегменты в [квадратных скобках] становятся параметрами.
Базовый динамический сегмент
app/posts/[id]/page.tsx:
export default async function Post({
params,
}: {
params: Promise<{ id: string }>;
}) {
const { id } = await params;
return <h1>Пост {id}</h1>;
}
URL: /posts/1, /posts/hello, /posts/anything.
Несколько параметров
app/[category]/[id]/page.tsx:
export default async function Page({
params,
}: {
params: Promise<{ category: string; id: string }>;
}) {
const { category, id } = await params;
return <p>{category} / {id}</p>;
}
Типы
Все параметры — строки. Если нужен number — преобразуй:
const { id } = await params;
const postId = Number(id);
if (isNaN(postId)) notFound();
Catch-all сегмент
[...slug] — ловит любое количество сегментов:
app/docs/[...slug]/page.tsx:
export default async function Docs({
params,
}: {
params: Promise<{ slug: string[] }>;
}) {
const { slug } = await params;
return <p>Путь: {slug.join('/')}</p>;
}
/docs/a→['a']/docs/a/b→['a', 'b']/docs/a/b/c→['a', 'b', 'c']
Но не /docs (без сегментов).
Опциональный catch-all
[[...slug]] — ловит и без сегментов:
app/docs/[[...slug]]/page.tsx:
const { slug } = await params;
// /docs → undefined
// /docs/a → ['a']
Проверка:
const path = slug?.join('/') ?? 'главная';
generateStaticParams
Предгенерация статических страниц:
export async function generateStaticParams() {
const posts = await getPosts();
return posts.map((post) => ({
id: String(post.id),
}));
}
Next.js сгенерирует HTML для всех этих URL на этапе сборки.
Для нескольких параметров:
export async function generateStaticParams() {
return [
{ category: 'tech', id: '1' },
{ category: 'life', id: '2' },
];
}
dynamicParams
Если URL не в generateStaticParams:
export const dynamicParams = true; // по умолчанию — генерируется на лету
false — 404 для всех остальных.
Пример: блог
app/
├── blog/
│ ├── page.tsx → /blog
│ └── [slug]/
│ └── page.tsx → /blog/my-post
// app/blog/[slug]/page.tsx
export async function generateStaticParams() {
const posts = await getPosts();
return posts.map((p) => ({ slug: p.slug }));
}
export default async function Post({ params }) {
const { slug } = await params;
const post = await getPostBySlug(slug);
if (!post) notFound();
return <article>{post.content}</article>;
}
Пример: документация
app/docs/[[...slug]]/page.tsx
/docs— главная документации/docs/getting-started— один раздел/docs/api/users— вложенный путь
notFound
import { notFound } from 'next/navigation';
const post = await getPost(id);
if (!post) notFound();
Показывает ближайший not-found.tsx.
Валидация параметров
const { id } = await params;
const postId = Number(id);
if (!Number.isInteger(postId) || postId <= 0) {
notFound();
}
Итоги
[id]— динамический сегмент[...slug]— catch-all[[...slug]]— опциональныйparams— промис- Все параметры — строки
generateStaticParamsдля SSGnotFound()для 404