Динамические маршруты
Основы
[param] — динамический параметр в имени файла:
pages/users/[id].vue
URL: /users/1, /users/abc, /users/anything
Доступ к параметрам
<script setup lang="ts">
const route = useRoute();
const id = computed(() => route.params.id);
</script>
<template>
<p>ID: {{ id }}</p>
</template>
Несколько параметров
pages/[lang]/blog/[slug].vue → /ru/blog/vue-intro
const lang = route.params.lang; // 'ru'
const slug = route.params.slug; // 'vue-intro'
Опциональные
[[id]] — двойные скобки:
pages/users/[[id]].vue
| URL | route.params.id |
|---|---|
/users | undefined |
/users/5 | '5' |
Catch-all
[...slug] — любое количество сегментов:
pages/[...slug].vue
| URL | route.params.slug |
|---|---|
/a | ['a'] |
/a/b/c | ['a', 'b', 'c'] |
Валидация параметров
definePageMeta с validate:
<script setup lang="ts">
definePageMeta({
validate: (route) => {
return /^\d+$/.test(route.params.id as string);
},
});
</script>
Если false — редирект на 404.
Или редирект:
<script setup lang="ts">
definePageMeta({
validate: (route) => {
if (route.params.id === 'admin') {
return { path: '/login' };
}
return true;
},
});
</script>
useRoute vs useRouter
const route = useRoute(); // текущий маршрут
route.params.id;
route.query.q;
const router = useRouter(); // навигация
router.push('/');
Реактивность
route — реактивный.
const id = computed(() => route.params.id);
watch(() => route.params.id, (newId) => {
loadUser(newId);
});
Переиспользование компонента
⚠️ Nuxt переиспользует компонент при смене параметров. onMounted не сработает.
Решение 1 — watch:
watch(
() => route.params.id,
(newId) => loadUser(newId)
);
Решение 2 — key:
<NuxtPage :key="route.fullPath" />
Решение 3 — через useFetch с реактивным URL:
const { data: user } = await useFetch(() => `/api/users/${route.params.id}`);
Nuxt сам перезапросит при смене URL.
Загрузка данных
useFetch — реактивный:
<script setup lang="ts">
const route = useRoute();
const { data: user, pending, error } = await useFetch(
() => `/api/users/${route.params.id}`
);
</script>
<template>
<p v-if="pending">Загрузка...</p>
<p v-else-if="error">{{ error.message }}</p>
<div v-else>
<h1>{{ user?.name }}</h1>
</div>
</template>
Что даёт:
- SSR из коробки
- Реактивность на изменение параметра
- Кеш
useRoute с типизацией
const route = useRoute();
const id = route.params.id as string;
Или через generic (Nuxt 3.9+):
const route = useRoute<'/users/[id]'>();
const id = route.params.id; // string
Практика — блог
pages/blog/[slug].vue:
<script setup lang="ts">
const route = useRoute();
const { data: post } = await useFetch(
() => `/api/posts/${route.params.slug}`
);
if (!post.value) {
throw createError({
statusCode: 404,
statusMessage: 'Post not found',
});
}
</script>
<template>
<article v-if="post">
<h1>{{ post.title }}</h1>
<div v-html="post.content" />
</article>
</template>
Catch-all для 404
pages/[...slug].vue:
<script setup lang="ts">
const route = useRoute();
throw createError({
statusCode: 404,
statusMessage: 'Page not found',
data: { path: route.fullPath },
});
</script>
Валидация нескольких параметров
definePageMeta({
validate: (route) => {
const { id, slug } = route.params;
return /^\d+$/.test(id as string) && (slug as string).length > 0;
},
});
Итог
[id]— динамика[[id]]— опционально[...slug]— catch-alluseRoute()— доступuseFetch(() => ...)— реактивная загрузкаvalidate— проверка
Дальше — layouts.