13. Plugins
🔌 Плагины в Nuxt 3
Заголовок раздела «🔌 Плагины в Nuxt 3»Плагины — это мощный механизм расширения функциональности Nuxt-приложения. Они позволяют подключать Vue-плагины, регистрировать глобальные компоненты, добавлять вспомогательные функции в контекст приложения и инициализировать сторонние библиотеки.
📁 Структура директории plugins/
Заголовок раздела «📁 Структура директории plugins/»Все плагины Nuxt 3 располагаются в директории plugins/. Nuxt автоматически обнаруживает и загружает файлы из этой директории:
plugins/├── my-plugin.ts # загружается на клиенте и сервере├── analytics.client.ts # только на клиенте├── db.server.ts # только на сервере└── 01.init.ts # порядок загрузки через префикс🛠️ Создание плагина с defineNuxtPlugin
Заголовок раздела «🛠️ Создание плагина с defineNuxtPlugin»Основа любого плагина — функция defineNuxtPlugin. Она принимает callback, который получает экземпляр Nuxt-приложения:
export default defineNuxtPlugin((nuxtApp) => { console.log('Плагин инициализирован!')})defineNuxtPlugin обеспечивает типизацию и интеграцию с системой плагинов Nuxt.
💉 Инъекция в контекст приложения
Заголовок раздела «💉 Инъекция в контекст приложения»Одна из ключевых возможностей плагинов — предоставление вспомогательных функций, доступных через useNuxtApp():
export default defineNuxtPlugin((nuxtApp) => { const api = { async get(url: string) { return await $fetch(url) }, async post(url: string, data: any) { return await $fetch(url, { method: 'POST', body: data }) } }
return { provide: { api } }})Теперь в компонентах можно использовать $api:
<script setup lang="ts">const { $api } = useNuxtApp()
const data = await $api.get('/api/users')</script>🎯 Типизация инъекций
Заголовок раздела «🎯 Типизация инъекций»Для полноценной TypeScript-поддержки нужно объявить типы инъекций:
export default defineNuxtPlugin(() => { const formatDate = (date: Date): string => { return new Intl.DateTimeFormat('ru-RU', { day: '2-digit', month: 'long', year: 'numeric' }).format(date) }
const formatCurrency = (amount: number, currency = 'RUB'): string => { return new Intl.NumberFormat('ru-RU', { style: 'currency', currency }).format(amount) }
return { provide: { formatDate, formatCurrency } }})// types/nuxt.d.ts или index.d.tsdeclare module '#app' { interface NuxtApp { $formatDate: (date: Date) => string $formatCurrency: (amount: number, currency?: string) => string }}
export {}🖥️ Серверные плагины (.server.ts)
Заголовок раздела «🖥️ Серверные плагины (.server.ts)»Файлы с суффиксом .server.ts выполняются только на стороне сервера:
import { PrismaClient } from '@prisma/client'
export default defineNuxtPlugin(() => { const prisma = new PrismaClient()
return { provide: { prisma } }})Это полезно для:
- Подключения к базам данных
- Инициализации серверных SDK
- Настройки логгирования на сервере
- Работы с файловой системой
💻 Клиентские плагины (.client.ts)
Заголовок раздела «💻 Клиентские плагины (.client.ts)»Файлы с суффиксом .client.ts выполняются только в браузере:
export default defineNuxtPlugin(() => { // Инициализация аналитики только в браузере if (typeof window !== 'undefined') { window.dataLayer = window.dataLayer || []
const gtag = (...args: any[]) => { window.dataLayer.push(args) }
gtag('js', new Date()) gtag('config', 'GA_MEASUREMENT_ID')
return { provide: { gtag } } }})import { toast } from 'vue-toastification'import 'vue-toastification/dist/index.css'
export default defineNuxtPlugin((nuxtApp) => { nuxtApp.vueApp.use(toast, { position: 'top-right', timeout: 3000 })
return { provide: { toast } }})📊 Порядок загрузки плагинов
Заголовок раздела «📊 Порядок загрузки плагинов»Nuxt загружает плагины в алфавитном порядке. Для явного управления порядком используйте числовые префиксы:
plugins/├── 01.sentry.ts # загружается первым├── 02.store.ts # загружается вторым├── 03.router.ts # загружается третьим└── analytics.ts # загружается последним (по алфавиту)// plugins/01.sentry.ts — инициализация до всего остальногоimport * as Sentry from '@sentry/vue'
export default defineNuxtPlugin((nuxtApp) => { Sentry.init({ app: nuxtApp.vueApp, dsn: 'YOUR_DSN', tracesSampleRate: 1.0 })})🔗 Регистрация Vue-плагинов
Заголовок раздела «🔗 Регистрация Vue-плагинов»Через Nuxt-плагины можно регистрировать любые Vue-плагины:
import { VueQueryPlugin, QueryClient } from '@tanstack/vue-query'
export default defineNuxtPlugin((nuxtApp) => { const queryClient = new QueryClient({ defaultOptions: { queries: { staleTime: 5 * 60 * 1000, // 5 минут retry: 2 } } })
nuxtApp.vueApp.use(VueQueryPlugin, { queryClient })
return { provide: { queryClient } }})export default defineNuxtPlugin((nuxtApp) => { // Регистрация кастомной директивы nuxtApp.vueApp.directive('focus', { mounted(el) { el.focus() } })
nuxtApp.vueApp.directive('click-outside', { mounted(el, binding) { el.clickOutsideEvent = (event: Event) => { if (!(el === event.target || el.contains(event.target as Node))) { binding.value() } } document.addEventListener('click', el.clickOutsideEvent) }, unmounted(el) { document.removeEventListener('click', el.clickOutsideEvent) } })})⚡ Async плагины
Заголовок раздела «⚡ Async плагины»Плагины могут быть асинхронными — Nuxt дождётся их инициализации перед рендерингом:
export default defineNuxtPlugin(async (nuxtApp) => { // Загрузка конфигурации перед стартом приложения const config = await $fetch('/api/app-config')
return { provide: { appConfig: config } }})⚠️ Осторожно: асинхронные плагины блокируют рендеринг. Используйте их только когда данные действительно нужны перед первым рендером.
🪝 Хуки плагинов
Заголовок раздела «🪝 Хуки плагинов»Плагины могут подписываться на жизненный цикл приложения:
export default defineNuxtPlugin((nuxtApp) => { // Вызывается при монтировании Vue-приложения nuxtApp.hook('app:mounted', () => { console.log('Приложение смонтировано') })
// Вызывается при каждой навигации nuxtApp.hook('page:start', () => { console.log('Начало загрузки страницы') })
nuxtApp.hook('page:finish', () => { console.log('Страница загружена') })
// Обработка ошибок nuxtApp.hook('vue:error', (error, instance, info) => { console.error('Vue ошибка:', error) // Отправка в Sentry или другой сервис })})🔧 Доступ к плагинам через useNuxtApp
Заголовок раздела «🔧 Доступ к плагинам через useNuxtApp»<script setup lang="ts">const nuxtApp = useNuxtApp()
// Доступ к инъекциямconst { $api, $formatDate, $toast } = nuxtApp
// Использованиеconst handleSubmit = async (data: FormData) => { try { const result = await $api.post('/api/submit', data) $toast.success('Успешно отправлено!') } catch (error) { $toast.error('Ошибка при отправке') }}</script>📝 Практический пример: плагин авторизации
Заголовок раздела «📝 Практический пример: плагин авторизации»export default defineNuxtPlugin(async (nuxtApp) => { const token = useCookie('auth_token') const user = useState('user', () => null)
if (token.value) { try { const userData = await $fetch('/api/auth/me', { headers: { Authorization: `Bearer \${token.value}` } }) user.value = userData } catch { token.value = null } }
const auth = { async login(credentials: { email: string; password: string }) { const response = await $fetch('/api/auth/login', { method: 'POST', body: credentials }) token.value = response.token user.value = response.user }, async logout() { token.value = null user.value = null await navigateTo('/login') }, get isAuthenticated() { return !!user.value } }
return { provide: { auth } }})🧪 Тестирование плагинов
Заголовок раздела «🧪 Тестирование плагинов»import { describe, it, expect } from 'vitest'import { mountSuspended } from '@nuxt/test-utils/runtime'import TestComponent from './TestComponent.vue'
describe('format plugin', () => { it('formats date correctly', async () => { const wrapper = await mountSuspended(TestComponent) expect(wrapper.text()).toContain('01 января 2024 г.') })})📌 Лучшие практики
Заголовок раздела «📌 Лучшие практики»| Практика | Описание |
|---|---|
| Разделяй по окружению | Используй .client.ts и .server.ts суффиксы |
| Контролируй порядок | Числовые префиксы для зависимых плагинов |
| Типизируй инъекции | Объявляй типы в declare module '#app' |
| Избегай тяжёлых async | Блокируют рендеринг страницы |
| Используй хуки | Подписывайся на lifecycle события |
| Один плагин — одна задача | Принцип единственной ответственности |