Файловая маршрутизация в Nuxt 3
Nuxt 3 использует файловую маршрутизацию (file-based routing): каждый Vue-файл в папке pages/ автоматически становится страницей приложения. Это избавляет от ручного описания маршрутов в router/index.ts — структура URL повторяет структуру папок и файлов.
В этой статье разберём базовые правила роутинга, динамические и вложенные маршруты, catch-all и optional catch-all, Route Groups, приоритет маршрутов, страницу 404, composables useRoute и useRouter, definePageMeta и программную навигацию navigateTo.
Содержание
- Базовая файловая маршрутизация
- index.vue и корневые маршруты
- Динамические параметры [id]
- Вложенные маршруты
- Catch-all, optional catch-all и страница 404
- Route Groups
- Приоритет маршрутов
- Валидация параметров validate
- useRoute и useRouter
- definePageMeta
- Программная навигация navigateTo
- Лучшие практики и антипаттерны
- Ключевые тезисы для интервью
- Заключение
- Полезные ссылки
Базовая файловая маршрутизация
Папка pages/ находится рядом с app.vue. Nuxt сканирует её содержимое и строит маршруты на основе имён файлов и папок.
pages/
├── index.vue # /
├── about.vue # /about
├── contact.vue # /contact
└── users/
├── index.vue # /users
└── [id].vue # /users/:id
| Файл | URL |
|---|---|
pages/index.vue | / |
pages/about.vue | /about |
pages/users/index.vue | /users |
pages/users/[id].vue | /users/:id |
Под капотом Nuxt генерирует конфигурацию Vue Router автоматически. Вручную прописывать маршруты не нужно.
Включение файловой маршрутизации
Если папка pages/ существует, Nuxt автоматически использует <NuxtPage /> в app.vue:
<!-- app.vue -->
<template>
<div>
<NuxtLayout>
<NuxtPage />
</NuxtLayout>
</div>
</template>
Компонент <NuxtPage /> — это аналог <RouterView /> из Vue Router, но с дополнительной интеграцией в Nuxt: поддержкой transitions, keepalive, layout и других функций фреймворка.
index.vue и корневые маршруты
Файл index.vue в любой папке отвечает за корневой маршрут этой папки.
pages/
├── index.vue # /
├── users/
│ ├── index.vue # /users
│ └── [id].vue # /users/:id
└── blog/
├── index.vue # /blog
└── [slug].vue # /blog/:slug
| Файл | URL |
|---|---|
pages/users/index.vue | /users |
pages/users/[id].vue | /users/42 |
pages/blog/[slug].vue | /blog/my-post |
Если для /users есть одновременно pages/users.vue и pages/users/index.vue, Nuxt отдаёт приоритет index.vue внутри папки. Вложенные маршруты требуют особой структуры (см. следующий раздел).
Динамические параметры [id]
Динамический сегмент URL обозначается квадратными скобками: [param].
pages/
└── users/
└── [id].vue # /users/:id
<!-- pages/users/[id].vue -->
<template>
<div>
<h1>Пользователь {{ userId }}</h1>
</div>
</template>
<script setup>
const route = useRoute();
const userId = route.params.id;
</script>
Несколько параметров
pages/
└── posts/
└── [category]/
└── [postId].vue # /posts/:category/:postId
URL /posts/vue/123 даст route.params.category === 'vue' и route.params.postId === '123'.
Типы параметров
Все параметры маршрута — строки. Если нужно число, преобразуйте вручную:
const route = useRoute();
const userId = Number(route.params.id);
Вложенные маршруты
Вложенные маршруты (nested routes) позволяют рендерить дочерние страницы внутри родительского <NuxtPage />.
pages/
├── users.vue # родительский layout-маршрут /users
└── users/
├── index.vue # /users
└── [id].vue # /users/:id
<!-- pages/users.vue -->
<template>
<div>
<h1>Пользователи</h1>
<NuxtPage />
</div>
</template>
<!-- pages/users/index.vue -->
<template>
<p>Список пользователей</p>
</template>
<!-- pages/users/[id].vue -->
<template>
<p>Профиль пользователя {{ $route.params.id }}</p>
</template>
При переходе на /users рендерится pages/users.vue + pages/users/index.vue. При переходе на /users/42 — pages/users.vue + pages/users/[id].vue.
Важно
Для вложенных маршрутов обязательно нужен родительский файл pages/users.vue с <NuxtPage />. Без него pages/users/index.vue и pages/users/[id].vue будут работать как независимые страницы.
Catch-all, optional catch-all и страница 404
Catch-all маршруты
Файл с тремя точками [...slug] ловит одно и более количество сегментов URL.
pages/
├── index.vue
└── [...slug].vue # /anything, /a/b/c
<!-- pages/[...slug].vue -->
<template>
<div>
<h1>Страница: {{ slugPath }}</h1>
</div>
</template>
<script setup>
const route = useRoute();
const slugPath = route.params.slug; // строка или массив
</script>
| URL | route.params.slug |
|---|---|
/docs | 'docs' |
/docs/vue | ['docs', 'vue'] |
/a/b/c/d | ['a', 'b', 'c', 'd'] |
Optional catch-all
Двойные квадратные скобки [[...slug]] делают catch-all необязательным: маршрут срабатывает и для базового пути, и для любого числа сегментов. Без совпавших сегментов параметр равен undefined.
pages/
├── index.vue
└── shop/
└── [[...categories]].vue # /shop, /shop/electronics, /shop/a/b
<!-- pages/shop/[[...categories]].vue -->
<template>
<div>
<h1 v-if="!categories">Все товары</h1>
<h1 v-else>Фильтр: {{ categories.join(' / ') }}</h1>
</div>
</template>
<script setup>
const route = useRoute();
const categories = route.params.categories; // пусто | строка | массив
</script>
| URL | route.params.categories |
|---|---|
/shop | пусто (falsy) |
/shop/electronics | 'electronics' |
/shop/electronics/laptops | ['electronics', 'laptops'] |
Ключевое отличие: [...slug] требует минимум один сегмент, а [[...slug]] охватывает и корневой путь. Для базового пути параметр приходит пустым (falsy) значением, поэтому проверяйте его через if (!categories), а не через сравнение с конкретным значением. Одиночный опциональный параметр без catch-all обозначается так же, но без точек — [[slug]].
Страница 404
Создайте pages/[...slug].vue, который будет отображать страницу «не найдено»:
<!-- pages/[...slug].vue -->
<script setup>
const route = useRoute();
</script>
<template>
<div>
<h1>404</h1>
<p>Страница {{ route.path }} не найдена</p>
</div>
</template>
Для более гибкой обработки ошибок используйте error.vue в корне проекта или throwError / showError.
Route Groups
Route Groups (доступны с Nuxt 3.13) позволяют группировать страницы в папки, которые не попадают в URL. Папка оборачивается в круглые скобки:
pages/
├── index.vue # /
├── (marketing)/
│ ├── about.vue # /about
│ └── contact.vue # /contact
└── (dashboard)/
├── settings.vue # /settings
└── profile.vue # /profile
Приложение получит маршруты /, /about, /contact, /settings, /profile — имена (marketing) и (dashboard) для URL-структуры игнорируются. Это аналог Route Groups из Next.js.
Зачем нужны
- Организация кода: логически близкие страницы лежат рядом, даже если их URL не вложены друг в друга.
- Общие правила через middleware: имя группы доступно в
route.meta.groups, поэтому middleware может применять логику к целой группе.
<!-- pages/(admin)/users.vue -->
<script setup>
const route = useRoute();
console.log(route.meta.groups); // ['admin']
</script>
// middleware/admin.ts
export default defineNuxtRouteMiddleware((to) => {
if (to.meta.groups?.includes('admin')) {
// проверка доступа ко всей группе (admin)
}
});
Группы можно вкладывать: файл pages/(app)/(admin)/users.vue даст route.meta.groups === ['app', 'admin'].
Приоритет маршрутов
Когда несколько файлов могут совпасть с одним URL, Nuxt (через Vue Router) ранжирует маршруты по специфичности пути:
- Статические сегменты — наивысший приоритет.
- Динамические сегменты (
[param]). - Catch-all (
[...slug],[[...slug]]) — низший приоритет.
pages/
├── shop/
│ ├── new.vue # /shop/new (статический)
│ ├── [category].vue # /shop/electronics (динамический)
│ └── [...slug].vue # /shop/a/b/c (catch-all)
Запрос /shop/new обслужит new.vue, потому что статический сегмент приоритетнее динамического [category].vue.
Отдельное правило для вложенных маршрутов
Именованный родительский файл приоритетнее вложенного динамического. Для /foo/hello маршрут pages/foo.vue выигрывает у pages/foo/[slug].vue:
pages/
├── foo.vue # /foo → имеет приоритет для /foo/hello
└── foo/
└── [slug].vue # /foo/:slug
Чтобы /foo и /foo/hello обслуживались разными страницами, используйте пару pages/foo/index.vue + pages/foo/[slug].vue.
Валидация параметров validate
Nuxt позволяет валидировать параметры маршрута через definePageMeta с функцией validate:
<!-- pages/users/[id].vue -->
<script setup>
definePageMeta({
validate: async (route) => {
return /^\=+$/.test(route.params.id);
},
});
</script>
Функция validate может быть синхронной или асинхронной. Если она возвращает false, Nuxt показывает страницу 404.
Валидация с navigateTo
Можно перенаправить пользователя вместо показа 404:
<!-- pages/users/[id].vue -->
<script setup>
definePageMeta({
validate: (route) => {
const id = Number(route.params.id);
if (Number.isNaN(id) || id <= 0) {
return navigateTo('/users');
}
return true;
},
});
</script>
useRoute и useRouter
Nuxt предоставляет два ключевых composables для работы с маршрутизацией:
useRoute
Возвращает текущий маршрут — объект с path, params, query, name, matched и другими полями Vue Router.
<script setup>
const route = useRoute();
console.log(route.path); // /users/42
console.log(route.params); // { id: '42' }
console.log(route.query); // { tab: 'settings' }
</script>
useRouter
Возвращает экземпляр Vue Router с методами навигации и управления историей.
<script setup>
const router = useRouter();
function goBack() {
router.back();
}
function goHome() {
router.push('/');
}
</script>
| Composable | Назначение |
|---|---|
useRoute() | Текущий маршрут: параметры, query, hash, мета-данные. |
useRouter() | Управление навигацией: push, replace, back, forward, go. |
definePageMeta
definePageMeta — макрос Nuxt для задания мета-данных конкретной страницы:
<!-- pages/admin.vue -->
<script setup>
definePageMeta({
layout: 'admin',
middleware: 'auth',
title: 'Админ-панель',
keepalive: true,
});
</script>
Распространённые поля
| Поле | Описание |
|---|---|
layout | Имя макета из layouts/. |
middleware | Route middleware, выполняемый перед загрузкой страницы. |
title | Заголовок страницы (может использоваться модулями или плагинами). |
keepalive | Включить keep-alive для этой страницы. |
validate | Функция валидации параметров маршрута. |
key | Кастомный ключ для <NuxtPage /> и transitions. |
Мета-данные страницы доступны через route.meta после перехода на маршрут.
Программная навигация navigateTo
navigateTo — предпочтительный способ программной навигации в Nuxt 3. Он корректно работает как на сервере (во время SSR), так и на клиенте.
<script setup>
const router = useRouter();
async function login() {
await $fetch('/api/login', { method: 'POST' });
await navigateTo('/dashboard');
}
</script>
navigateTo vs router.push
navigateTo | router.push | |
|---|---|---|
| Работает на сервере | Да | Нет |
| Работает на клиенте | Да | Да |
| Поддерживает внешние URL | Да | Нет |
| Рекомендуется в Nuxt | Да | В ограниченных случаях |
// Внешний редирект
await navigateTo('https://example.com', { external: true });
// Замена текущей записи в истории
await navigateTo('/login', { replace: true });
navigateTo можно использовать в middleware, плагинах, обработчиках событий и validate.
Лучшие практики и антипаттерны
✅ Делайте
- Используйте
useRoute()для чтения параметров и query в<script setup>. - Используйте
navigateToдля программной навигации, особенно в SSR-контексте. - Валидируйте параметры через
definePageMeta({ validate }). - Используйте вложенные маршруты, когда дочерние страницы должны рендериться внутри общего layout-файла.
- Используйте
[[...slug]], когда catch-all должен покрывать и корневой путь, а не только вложенные URL. - Группируйте страницы через
(group)/, чтобы организовать код без раздувания URL-иерархии. - Давайте файлам понятные имена, соответствующие URL.
❌ Не делайте
- Не используйте
window.locationилиhistory.pushStateнапрямую — это ломает SSR и гидратацию. - Не забывайте, что
route.paramsвсегда содержит строки, даже для числовых id. - Не смешивайте в одной папке
users.vueиusers/index.vue, если ожидаете вложенные маршруты — разберитесь в приоритетах. - Не делайте сложную бизнес-логику прямо в
validate; лучше вынести её в composable или API.
Ключевые тезисы для интервью
- Файловая маршрутизация: каждый файл/папка в
pages/задаёт сегмент URL, аindex.vueотвечает за корень папки; конфигурация Vue Router генерируется автоматически. - Динамические
[param]покрывают один обязательный сегмент,[...slug]— один и более,[[...slug]]— ноль и более (optional catch-all); все параметры приходят строками. - Вложенные маршруты требуют родительского файла с
<NuxtPage />, иначе дочерние страницы работают независимо. - Route Groups
(group)/группируют страницы без влияния на URL, а имя группы доступно вroute.meta.groups. - Приоритет маршрутов: статические > динамические
[param]> catch-all; именованный родительfoo.vueприоритетнее вложенногоfoo/[slug].vue. useRouteчитает текущий маршрут,useRouterуправляет навигацией,navigateTo— SSR-safe переход;definePageMetaзадаёт мета-данные (layout, middleware, validate).validateвdefinePageMetaпроверяет параметры (иначе 404 или редирект черезnavigateTo); страницу 404 делают через[...slug].vueилиerror.vue.
Заключение
Файловая маршрутизация — одно из главных преимуществ Nuxt 3. Она ускоряет разработку, делает структуру проекта предсказуемой и сразу отражает URL-поверхность приложения. Зная правила именования файлов, динамические параметры, вложенные маршруты, catch-all и optional catch-all, Route Groups, приоритет маршрутов, useRoute, useRouter, definePageMeta и navigateTo, можно уверенно строить навигацию в Nuxt-приложениях любой сложности.
Следующие статьи раздела продолжат погружение: режимы рендеринга, composables Nuxt, жизненный цикл приложения, серверная часть Nitro, data fetching и работа с состоянием.
Полезные ссылки
- Nuxt 3 Routing — официальная документация по роутингу
- Vue Router — документация базового роутера
- Nuxt Pages — структура папки pages, динамические и catch-all маршруты, Route Groups
- Nuxt 3.13 — Route Groups — релиз, где появились группы маршрутов
- definePageMeta — мета-данные страницы
- navigateTo — программная навигация