Динамические маршруты и параметры
Квадратные скобки в имени файла превращают часть пути в параметр: один файл [id].vue обслуживает тысячи товаров.
Суть: динамический сегмент маршрута объявляется через квадратные скобки в имени файла, например pages/products/[id].vue. Значение параметра читается через useRoute().params. Так один шаблон обслуживает любой идентификатор в URL.
Статичные маршруты хороши, пока страниц немного. Но что делать с каталогом из тысяч товаров? Создавать файл на каждый — безумие. Здесь на сцену выходят динамические маршруты: один файл-шаблон обслуживает любое значение в части пути.
Синтаксис — квадратные скобки в имени файла. pages/products/[id].vue матчит /products/1, /products/42, /products/любая-строка. Часть пути на месте [id] становится параметром с именем id. Внутри компонента вы читаете его через useRoute():
<script setup>
const route = useRoute()
const id = route.params.id // "42" для /products/42
</script>
<template>
<h1>Товар №{{ id }}</h1>
</template>
Параметров может быть несколько: pages/users/[userId]/posts/[postId].vue матчит /users/7/posts/100 и даёт params.userId и params.postId. Есть и «всеохватный» (catch-all) сегмент через троеточие: pages/docs/[...slug].vue поймает /docs/a, /docs/a/b, /docs/a/b/c — удобно для документации с произвольной вложенностью.
Шаблон файла Совпадает с URL [id].vue -> /products/42 (id = "42") [userId]/[postId] -> /users/7/posts/100 (два параметра) [...slug].vue -> /docs/a/b/c (slug = ["a","b","c"])
Как работает под капотом
Из имени файла Nuxt строит шаблон маршрута для Vue Router. [id] превращается в динамический сегмент :id. При навигации роутер сопоставляет URL с зарегистрированными шаблонами, извлекает значения параметров и складывает их в route.params. Catch-all-сегмент собирает «хвост» пути в массив.
Смоделируем сам матчер — функцию, которая по шаблону и URL извлекает параметры. Это упрощённое сердце любого роутера:
// Мини-матчер: шаблон с :param против реального пути.
function matchRoute(pattern, url) {
const pSeg = pattern.split("/").filter(Boolean);
const uSeg = url.split("/").filter(Boolean);
if (pSeg.length !== uSeg.length) return null;
const params = {};
for (let i = 0; i < pSeg.length; i++) {
if (pSeg[i].startsWith(":")) {
params[pSeg[i].slice(1)] = uSeg[i]; // динамический сегмент
} else if (pSeg[i] !== uSeg[i]) {
return null; // статический не совпал
}
}
return params;
}
console.log(matchRoute("/products/:id", "/products/42"));
console.log(matchRoute("/users/:userId/posts/:postId", "/users/7/posts/100"));
console.log(matchRoute("/products/:id", "/about")); // null — длины разные
Попробуй сам ▶ — именно так роутер вытаскивает params.id из URL. Nuxt лишь генерирует эти шаблоны из имён файлов.
Частые ошибки
- Считать параметр числом.
route.params.id— всегда строка. Для арифметики приводите черезNumber(...). - Не обрабатывать несуществующий id. Если товара нет, верните 404 через
createError, а не показывайте пустую страницу. - Путать
paramsиquery./products/42— это params;/products?sort=price— этоroute.query.
Best practices
- Один динамический шаблон вместо тысяч статичных файлов — стандарт для каталогов и блогов.
- Для произвольной вложенности (документация, файловые пути) используйте catch-all
[...slug]. - Всегда валидируйте параметр и корректно отдавайте 404 для несуществующих сущностей.
Итог: динамические маршруты позволяют одному файлу обслуживать бесконечно много URL. Скобки задают параметр, useRoute().params его читает. Дальше посмотрим, как оборачивать страницы в общие макеты — layouts.
Динамические маршруты тесно связаны с темой получения данных, к которой мы придём позже. Типичный сценарий: на странице [id].vue вы читаете параметр, подставляете его в запрос к API и грузите данные именно этой сущности. При смене параметра (переход с товара 42 на товар 43) Nuxt умеет переиспользовать компонент и перезапросить данные, а не пересоздавать страницу с нуля. Понимание этой связки — параметр маршрута плюс реактивная загрузка по нему — открывает большинство реальных страниц-деталей: карточка товара, профиль пользователя, статья блога.