Динамические маршруты: [id] и params
Делаем один шаблон страницы для тысяч URL вида /blog/первый-пост, /blog/второй-пост.
Динамический сегмент — папка, имя которой обёрнуто в квадратные скобки (
[id]); её значение из URL попадает в компонент через объектparams.
Зачем это нужно
У вас сотни статей. Заводить по папке на каждую — безумие. Вместо этого создают один шаблон, который принимает идентификатор из адреса:
app/
└─ blog/
└─ [slug]/
└─ page.tsx → /blog/любая-строка
Чтение params
Страница получает проп params с именем сегмента в роли ключа. Для /blog/hello придёт { slug: "hello" }:
// app/blog/[slug]/page.tsx
export default function PostPage({ params }) {
return (
<article>
<h1>Статья: {params.slug}</h1>
</article>
);
}
Так адреса /blog/react, /blog/nextjs, /blog/css обслуживаются одним файлом, а params.slug подскажет, какую статью грузить.
Несколько динамических сегментов
Их можно вкладывать. Путь app/shop/[category]/[product]/page.tsx для /shop/phones/iphone даст params = { category: "phones", product: "iphone" }.
Catch-all сегменты
Иногда нужно поймать произвольную глубину пути. Папка [...slug] соберёт все оставшиеся сегменты в массив:
| Папка | URL | params |
[id] | /post/42 | { id: "42" } |
[...path] | /docs/a/b/c | { path: ["a","b","c"] } |
Как сопоставляется маршрут
Идея сопоставления статической и динамической части — на чистом JS:
function matchRoute(pattern, url) {
const p = pattern.split("/").filter(Boolean);
const u = url.split("/").filter(Boolean);
if (p.length !== u.length) return null;
const params = {};
for (let i = 0; i < p.length; i++) {
if (p[i].startsWith("[")) params[p[i].slice(1, -1)] = u[i];
else if (p[i] !== u[i]) return null;
}
return params;
}
console.log(matchRoute("/blog/[slug]", "/blog/nextjs"));
console.log(matchRoute("/blog/[slug]", "/about"));
Вывод:
{ slug: 'nextjs' }
null
Итог
- Папка
[id]— динамический сегмент, его значение приходит вparams. - Один шаблон обслуживает множество адресов — не нужно по папке на элемент.
[...slug]ловит произвольную глубину пути в массив.