Динамические маршруты: [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] соберёт все оставшиеся сегменты в массив:

ПапкаURLparams
[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] ловит произвольную глубину пути в массив.
Проверьте себя
1. Как объявить динамический сегмент маршрута для одной статьи?
AПапка (slug)
BПапка [slug]
CПапка {slug}
DФайл slug.tsx
2. Что придёт в params для маршрута app/blog/[slug]/page.tsx при URL /blog/react?
A{ slug: 'react' }
B{ blog: 'react' }
C['react']
D{ id: 'react' }
3. Чем отличается сегмент [...path] от [id]?
A[...path] работает только в Pages Router
B[...path] ловит произвольную глубину пути и собирает сегменты в массив
C[...path] обязателен для каждой страницы
D[...path] кэширует страницу
Поддержать проект