15. Middleware
🛡️ Middleware маршрутов в Nuxt 3
Заголовок раздела «🛡️ Middleware маршрутов в Nuxt 3»Middleware маршрутов позволяют выполнять код перед переходом на страницу. Это ключевой механизм для защиты маршрутов, редиректов, логирования навигации и проверки прав доступа.
📁 Виды middleware
Заголовок раздела «📁 Виды middleware»Nuxt поддерживает три вида middleware:
middleware/├── auth.ts # именованный middleware├── admin.ts # именованный middleware├── log.global.ts # глобальный (выполняется для всех страниц)└── analytics.global.ts # глобальный1. Именованные middleware
Заголовок раздела «1. Именованные middleware»Подключаются явно через definePageMeta:
<script setup>definePageMeta({ middleware: ['auth', 'admin']})</script>2. Глобальные middleware (*.global.ts)
Заголовок раздела «2. Глобальные middleware (*.global.ts)»Выполняются автоматически при каждой навигации:
export default defineNuxtRouteMiddleware((to, from) => { console.log(`Навигация: ${from.path} → ${to.path}`)})3. Инлайн middleware
Заголовок раздела «3. Инлайн middleware»Определяются прямо в компоненте страницы:
<script setup>definePageMeta({ middleware: defineNuxtRouteMiddleware((to, from) => { // Логика прямо в компоненте })})</script>🔐 Middleware аутентификации
Заголовок раздела «🔐 Middleware аутентификации»Самый распространённый паттерн — защита маршрутов:
export default defineNuxtRouteMiddleware((to, from) => { const user = useSupabaseUser() // или const { status } = useAuth()
if (!user.value) { return navigateTo('/login', { redirectCode: 301, query: { redirect: to.fullPath } }) }})<script setup lang="ts">definePageMeta({ middleware: 'auth'})</script>↩️ navigateTo
Заголовок раздела «↩️ navigateTo»navigateTo — утилита для программной навигации внутри middleware:
export default defineNuxtRouteMiddleware((to, from) => { // Простой редирект if (to.path === '/old-path') { return navigateTo('/new-path') }
// Редирект с кодом статуса if (to.path === '/moved') { return navigateTo('https://new-site.com', { external: true, redirectCode: 301 }) }
// Редирект с сохранением query-параметров if (!to.query.ref) { return navigateTo({ path: to.path, query: { ...to.query, ref: 'default' } }) }})🚫 abortNavigation
Заголовок раздела «🚫 abortNavigation»Останавливает навигацию без редиректа:
export default defineNuxtRouteMiddleware((to) => { const config = useRuntimeConfig()
if (config.public.maintenanceMode) { // Остановить навигацию с кодом ошибки return abortNavigation({ statusCode: 503, message: 'Сайт на техническом обслуживании' }) }})export default defineNuxtRouteMiddleware((to) => { const user = useUser()
if (to.meta.requiresAdmin && !user.value?.isAdmin) { // Просто отменить переход return abortNavigation() }})🔑 Middleware проверки ролей
Заголовок раздела «🔑 Middleware проверки ролей»export default defineNuxtRouteMiddleware(async (to) => { const { $auth } = useNuxtApp()
if (!$auth.isAuthenticated) { return navigateTo('/login') }
if (!$auth.user?.roles.includes('admin')) { return navigateTo('/403') }})export default defineNuxtRouteMiddleware((to) => { const userStore = useUserStore() const requiredPermission = to.meta.permission as string
if (requiredPermission && !userStore.hasPermission(requiredPermission)) { return abortNavigation({ statusCode: 403, message: `Недостаточно прав: требуется "${requiredPermission}"` }) }})Использование:
<script setup lang="ts">definePageMeta({ middleware: ['auth', 'permissions'], permission: 'users:manage'})</script>🌍 Глобальные middleware
Заголовок раздела «🌍 Глобальные middleware»export default defineNuxtRouteMiddleware((to, from) => { // Отправка события в аналитику при каждом переходе if (process.client) { const { $gtag } = useNuxtApp() $gtag?.('event', 'page_view', { page_title: document.title, page_path: to.fullPath }) }})export default defineNuxtRouteMiddleware((to) => { const { locale, availableLocales, defaultLocale } = useI18n()
// Определение языка из URL const pathLocale = to.path.split('/')[1] if (availableLocales.includes(pathLocale)) { locale.value = pathLocale } else { locale.value = defaultLocale }})⏱️ Порядок выполнения middleware
Заголовок раздела «⏱️ Порядок выполнения middleware»Важно понимать порядок выполнения при нескольких middleware:
1. Глобальные middleware (в алфавитном порядке)2. Именованные middleware (в порядке объявления в массиве)3. Инлайн middleware<script setup lang="ts">definePageMeta({ // Порядок: сначала auth, потом admin middleware: ['auth', 'admin']})</script>🖥️ Серверный middleware vs Route Middleware
Заголовок раздела «🖥️ Серверный middleware vs Route Middleware»В Nuxt 3 существуют два разных типа middleware:
Route Middleware (клиент + SSR)
Заголовок раздела «Route Middleware (клиент + SSR)»Файлы в middleware/ — выполняются перед навигацией.
Server Middleware (только сервер)
Заголовок раздела «Server Middleware (только сервер)»Файлы в server/middleware/ — выполняются для каждого HTTP-запроса:
export default defineEventHandler((event) => { setResponseHeaders(event, { 'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE', 'Access-Control-Allow-Headers': 'Content-Type, Authorization' })
if (getMethod(event) === 'OPTIONS') { event.node.res.statusCode = 204 return null }})export default defineEventHandler(async (event) => { const token = getHeader(event, 'authorization')?.replace('Bearer ', '')
if (event.path.startsWith('/api/protected')) { if (!token) { throw createError({ statusCode: 401, message: 'Требуется авторизация' }) }
try { const payload = verifyToken(token) event.context.user = payload } catch { throw createError({ statusCode: 401, message: 'Недействительный токен' }) } }})🧪 Тестирование middleware
Заголовок раздела «🧪 Тестирование middleware»import { describe, it, expect, vi } from 'vitest'import { mockNuxtImport } from '@nuxt/test-utils/runtime'
const { useUser } = await mockNuxtImport('useUser', () => ({ useUser: vi.fn()}))
describe('auth middleware', () => { it('redirects unauthenticated users to /login', async () => { useUser.mockReturnValue(ref(null))
const middleware = await import('~/middleware/auth') const to = { path: '/dashboard', fullPath: '/dashboard' } const from = { path: '/' }
const result = middleware.default(to, from) expect(result).toEqual(expect.objectContaining({ path: '/login' })) })})📌 Паттерны использования
Заголовок раздела «📌 Паттерны использования»| Паттерн | Middleware | Применение |
|---|---|---|
| Проверка авторизации | auth.ts | Защита всех приватных страниц |
| Проверка ролей | admin.ts | Разграничение доступа |
| Редирект | redirect.ts | Переадресация устаревших URLs |
| Аналитика | analytics.global.ts | Трекинг всех переходов |
| Техобслуживание | maintenance.global.ts | Режим обслуживания |
| Локаль | locale.global.ts | Определение языка |