Middleware, плагины, Nuxt modules, SEO с useHead, оптимизация изображений, runtimeConfig и деплой
В этой теме рассмотрим продвинутые возможности Nuxt 3: middleware, плагины, модули, SEO-инструменты, оптимизацию изображений, переменные окружения и деплой.
Route middleware в Nuxt — это функции, которые выполняются перед навигацией к определённому маршруту. Это аналог navigation guards в Vue Router, но с более удобной интеграцией.
Существуют три вида маршрутного middleware:
Inline middleware определяется прямо в компоненте страницы через definePageMeta:
<!-- pages/dashboard.vue -->
<script setup>
definePageMeta({
middleware: [
async (to, from) => {
const authStore = useAuthStore()
if (!authStore.isLoggedIn) {
return navigateTo('/login')
}
}
]
})
</script>Named middleware хранится в папке middleware/ и переиспользуется:
// middleware/auth.ts
export default defineNuxtRouteMiddleware((to, from) => {
const authStore = useAuthStore()
if (!authStore.isLoggedIn) {
// navigateTo — глобальная функция Nuxt для редиректа
return navigateTo({
path: '/login',
query: { redirect: to.fullPath }
})
}
})// middleware/admin.ts
export default defineNuxtRouteMiddleware((to, from) => {
const authStore = useAuthStore()
if (!authStore.user?.role === 'admin') {
throw createError({
statusCode: 403,
statusMessage: 'Доступ запрещён'
})
}
})Использование named middleware в страницах:
<!-- pages/admin/users.vue -->
<script setup>
definePageMeta({
// Middleware выполняются по порядку
middleware: ['auth', 'admin']
})
</script>Global middleware — файлы с суффиксом .global.ts применяются ко всем маршрутам автоматически:
// middleware/analytics.global.ts
export default defineNuxtRouteMiddleware((to, from) => {
// Трекинг всех переходов
if (process.client) {
analytics.track('page_view', { path: to.path })
}
})Серверный middleware работает на уровне HTTP-сервера Nitro, до выполнения маршрутов. Используется для логирования, CORS, аутентификации через куки:
// server/middleware/auth.ts
export default defineEventHandler(async (event) => {
// Этот код выполняется для КАЖДОГО запроса к серверу
// Пропускаем публичные маршруты
const publicRoutes = ['/api/auth/login', '/api/auth/register', '/api/public']
if (publicRoutes.some(route => event.path.startsWith(route))) {
return
}
// Пропускаем не-API маршруты
if (!event.path.startsWith('/api/')) {
return
}
const token = getCookie(event, 'access_token') ||
getHeader(event, 'Authorization')?.replace('Bearer ', '')
if (!token) {
throw createError({ statusCode: 401, statusMessage: 'Не авторизован' })
}
try {
const payload = verifyJWT(token, process.env.JWT_SECRET)
// Добавляем данные пользователя в контекст запроса
event.context.auth = { userId: payload.sub, role: payload.role }
} catch {
throw createError({ statusCode: 401, statusMessage: 'Токен недействителен' })
}
})// server/middleware/logger.ts
export default defineEventHandler((event) => {
const start = Date.now()
const method = event.method
const path = event.path
// onAfterResponse вызывается после отправки ответа
event.waitUntil(
Promise.resolve().then(() => {
const duration = Date.now() - start
console.log(`${method} ${path} — ${duration}ms`)
})
)
})Плагины в Nuxt позволяют добавлять глобальные Vue-плагины, регистрировать глобальные компоненты, расширять nuxtApp и выполнять код при инициализации приложения:
// plugins/myPlugin.ts
// Этот плагин выполняется при старте приложения (SSR и клиент)
export default defineNuxtPlugin((nuxtApp) => {
// Регистрируем Vue плагин
nuxtApp.vueApp.use(MyVuePlugin)
// Добавляем глобальные свойства
nuxtApp.provide('formatDate', (date: Date) => {
return new Intl.DateTimeFormat('ru-RU').format(date)
})
// Добавляем глобальный обработчик ошибок
nuxtApp.vueApp.config.errorHandler = (error, instance, info) => {
console.error('Vue error:', error, info)
// Отправляем в Sentry/другой сервис мониторинга
}
})Использование провайденного значения в компоненте:
<script setup>
// { $formatDate } автоматически доступно через provide
const { $formatDate } = useNuxtApp()
const formattedDate = $formatDate(new Date())
</script>Client-only и server-only плагины — суффиксы в именах файлов:
// plugins/analytics.client.ts ← только на клиенте
export default defineNuxtPlugin(() => {
// window доступен только на клиенте
window.dataLayer = window.dataLayer || []
// Инициализируем Google Analytics
})// plugins/database.server.ts ← только на сервере
export default defineNuxtPlugin(async (nuxtApp) => {
// Подключаемся к БД только на сервере
const db = await connectDatabase(process.env.DATABASE_URL)
nuxtApp.provide('db', db)
})Async плагины — для операций, требующих ожидания:
// plugins/auth.ts
export default defineNuxtPlugin(async (nuxtApp) => {
const authStore = useAuthStore()
// Восстанавливаем сессию при старте
try {
await authStore.restoreSession()
} catch {
// Тихо игнорируем — пользователь просто не залогинен
}
})Модули — это пакеты, которые расширяют возможности Nuxt. Они добавляют компоненты, composables, серверные маршруты и настройки конфигурации:
// nuxt.config.ts — подключение модулей
export default defineNuxtConfig({
modules: [
'@pinia/nuxt', // Pinia store management
'@nuxtjs/tailwindcss', // Tailwind CSS
'@nuxt/image', // Оптимизация изображений
'@nuxtjs/color-mode', // Dark mode
'@nuxtjs/i18n', // Интернационализация
'nuxt-svgo' // SVG как компоненты
],
// Конфигурация конкретного модуля
colorMode: {
preference: 'system',
fallback: 'light'
},
image: {
quality: 80,
domains: ['cdn.example.com']
}
})Создание простого кастомного модуля:
// modules/hello/index.ts
import { defineNuxtModule, addPlugin, createResolver } from '@nuxt/kit'
export default defineNuxtModule({
meta: {
name: 'hello',
configKey: 'hello'
},
defaults: {
greeting: 'Привет'
},
setup(options, nuxt) {
const resolver = createResolver(import.meta.url)
// Добавляем плагин
addPlugin(resolver.resolve('./plugin'))
// Добавляем composable
nuxt.options.imports.dirs ||= []
nuxt.options.imports.dirs.push(resolver.resolve('./composables'))
}
})Nuxt предоставляет удобные composables для управления <head> страницы:
<!-- pages/about.vue -->
<script setup>
// useHead — полный контроль над <head>
useHead({
title: 'О нас — My Company',
meta: [
{ name: 'description', content: 'Мы создаём отличные продукты' },
{ name: 'keywords', content: 'компания, продукты, разработка' }
],
link: [
{ rel: 'canonical', href: 'https://example.com/about' }
],
script: [
// Добавить скрипт для конкретной страницы
]
})
</script>useSeoMeta — более удобный API с TypeScript-поддержкой для всех SEO-метатегов:
<!-- pages/blog/[slug].vue -->
<script setup>
const route = useRoute()
const { data: post } = await useFetch(`/api/posts/${route.params.slug}`)
// useSeoMeta с динамическими данными
useSeoMeta({
title: () => `${post.value?.title} | My Blog`,
description: () => post.value?.excerpt,
// Open Graph
ogTitle: () => post.value?.title,
ogDescription: () => post.value?.excerpt,
ogImage: () => post.value?.coverImage,
ogUrl: () => `https://example.com/blog/${route.params.slug}`,
ogType: 'article',
// Twitter Card
twitterCard: 'summary_large_image',
twitterTitle: () => post.value?.title,
twitterImage: () => post.value?.coverImage
})
</script>Глобальные SEO-настройки в nuxt.config.ts:
export default defineNuxtConfig({
app: {
head: {
htmlAttrs: { lang: 'ru' },
titleTemplate: '%s — My App', // %s заменяется на title страницы
meta: [
{ charset: 'utf-8' },
{ name: 'viewport', content: 'width=device-width, initial-scale=1' },
{ name: 'description', content: 'Описание сайта по умолчанию' }
]
}
}
})definePageMeta для статических мета-данных страницы:
<script setup>
definePageMeta({
title: 'Контакты', // Для маршрута (доступно в middleware)
description: 'Свяжитесь с нами',
layout: 'default',
middleware: ['auth'],
// Кастомные данные для middleware/layout
requiresAuth: true,
roles: ['admin', 'moderator']
})
</script>Модуль @nuxt/image автоматически оптимизирует изображения: изменяет размер, конвертирует в WebP, ленивую загрузку:
npm install @nuxt/image// nuxt.config.ts
export default defineNuxtConfig({
modules: ['@nuxt/image'],
image: {
quality: 80,
// Разрешённые внешние домены
domains: ['images.unsplash.com', 'cdn.example.com'],
// Предустановленные размеры
screens: {
xs: 320, sm: 640, md: 768, lg: 1024, xl: 1280
}
}
})Использование <NuxtImg> и <NuxtPicture> в шаблонах:
<template>
<!-- NuxtImg — оптимизированный img тег -->
<NuxtImg
src="/uploads/photo.jpg"
alt="Фото"
width="800"
height="600"
:modifiers="{ quality: 90 }"
loading="lazy"
/>
<!-- Responsive изображение с разными размерами -->
<NuxtImg
src="/hero.jpg"
sizes="100vw sm:50vw lg:400px"
alt="Hero"
/>
<!-- NuxtPicture — picture тег с fallback -->
<NuxtPicture
src="/photo.jpg"
:imgAttrs="{ class: 'rounded-lg' }"
alt="Фото в современных форматах"
/>
</template>Nuxt разделяет переменные окружения на серверные (недоступны клиенту) и публичные:
// nuxt.config.ts
export default defineNuxtConfig({
runtimeConfig: {
// Только на сервере — доступны в server routes и серверных плагинах
jwtSecret: process.env.JWT_SECRET,
databaseUrl: process.env.DATABASE_URL,
smtpPassword: process.env.SMTP_PASSWORD,
public: {
// Доступны и на сервере и на клиенте
apiBase: process.env.API_BASE || '/api',
appVersion: '1.0.0',
googleMapsKey: process.env.GOOGLE_MAPS_KEY
}
}
})Файл .env:
JWT_SECRET=super-secret-key-min-32-chars
DATABASE_URL=postgresql://user:pass@localhost/mydb
SMTP_PASSWORD=email-password
API_BASE=https://api.example.com
GOOGLE_MAPS_KEY=AIza...
Использование в компонентах и server routes:
<!-- Компонент (клиент + сервер) — только public -->
<script setup>
const config = useRuntimeConfig()
// config.public.apiBase — доступно
// config.jwtSecret — undefined на клиенте (только на сервере!)
const { data } = await useFetch(`${config.public.apiBase}/posts`)
</script>// server/api/protected.ts — server route — все переменные доступны
export default defineEventHandler(async (event) => {
const config = useRuntimeConfig()
// config.jwtSecret — доступно!
const payload = verifyJWT(token, config.jwtSecret)
return payload
})Nuxt создаёт оптимизированную сборку в зависимости от выбранного режима.
SSR деплой на Node.js — наиболее мощный вариант с поддержкой всех возможностей:
# Сборка для production
npx nuxi build
# Запуск
node .output/server/index.mjsDockerfile для SSR-деплоя:
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
FROM node:20-alpine AS runtime
WORKDIR /app
COPY /app/.output ./output
ENV NODE_ENV=production
ENV PORT=3000
EXPOSE 3000
CMD ["node", "output/server/index.mjs"]SSG деплой — статические файлы на любой хостинг:
# Генерация всех страниц
npx nuxi generate
# Файлы в .output/public/ — загружаем на Vercel/Netlify/S3Vercel деплой — автоматически определяет Nuxt и настраивает SSR:
// vercel.json (опционально — Vercel всё умеет сам)
{
"framework": "nuxt"
}Netlify деплой — через файл конфигурации:
# netlify.toml
[build]
command = "npm run generate"
publish = ".output/public"
[[redirects]]
from = "/*"
to = "/index.html"
status = 200Nuxt поддерживает концепцию пресетов деплоя — автоматически настраивает сборку под конкретную платформу:
// nuxt.config.ts — выбор пресета
export default defineNuxtConfig({
nitro: {
preset: 'vercel' // или 'netlify', 'cloudflare', 'aws-lambda', 'node-server'
}
})При деплое на Vercel или Netlify пресет выбирается автоматически по CI/CD переменным.
Несколько важных практик для продакшен Nuxt-приложений.
Обработка глобальных ошибок через error.vue:
<!-- error.vue — глобальная страница ошибки -->
<template>
<div>
<h1>{{ error.statusCode }}</h1>
<p>{{ error.statusMessage }}</p>
<button @click="handleError">На главную</button>
</div>
</template>
<script setup>
const props = defineProps({
error: Object
})
function handleError() {
clearError({ redirect: '/' })
}
</script>Защита от ошибок в компонентах через <NuxtErrorBoundary>:
<template>
<NuxtErrorBoundary @error="logError">
<template #error="{ error, clearError }">
<p>Компонент упал: {{ error.message }}</p>
<button @click="clearError()">Попробовать снова</button>
</template>
<HeavyComponent />
</NuxtErrorBoundary>
</template>Продвинутый Nuxt 3 открывает возможности enterprise-разработки: route middleware для контроля доступа, серверный middleware для обработки всех запросов, плагины для расширения функциональности, богатая экосистема модулей. SEO-инструменты useHead и useSeoMeta делают SEO-оптимизацию простой задачей. runtimeConfig обеспечивает безопасное управление секретами. А гибкие варианты деплоя позволяют запустить Nuxt-приложение на любой платформе.
Вопросы ещё не добавлены
Вопросы для этой подтемы ещё не добавлены.