Перейти к содержимому

13. Plugins

Плагины — это мощный механизм расширения функциональности Nuxt-приложения. Они позволяют подключать Vue-плагины, регистрировать глобальные компоненты, добавлять вспомогательные функции в контекст приложения и инициализировать сторонние библиотеки.


Все плагины Nuxt 3 располагаются в директории plugins/. Nuxt автоматически обнаруживает и загружает файлы из этой директории:

plugins/
├── my-plugin.ts # загружается на клиенте и сервере
├── analytics.client.ts # только на клиенте
├── db.server.ts # только на сервере
└── 01.init.ts # порядок загрузки через префикс

Основа любого плагина — функция defineNuxtPlugin. Она принимает callback, который получает экземпляр Nuxt-приложения:

plugins/my-plugin.ts
export default defineNuxtPlugin((nuxtApp) => {
console.log('Плагин инициализирован!')
})

defineNuxtPlugin обеспечивает типизацию и интеграцию с системой плагинов Nuxt.


Одна из ключевых возможностей плагинов — предоставление вспомогательных функций, доступных через useNuxtApp():

plugins/api.ts
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-поддержки нужно объявить типы инъекций:

plugins/format.ts
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.ts
declare module '#app' {
interface NuxtApp {
$formatDate: (date: Date) => string
$formatCurrency: (amount: number, currency?: string) => string
}
}
export {}

Файлы с суффиксом .server.ts выполняются только на стороне сервера:

plugins/database.server.ts
import { PrismaClient } from '@prisma/client'
export default defineNuxtPlugin(() => {
const prisma = new PrismaClient()
return {
provide: {
prisma
}
}
})

Это полезно для:

  • Подключения к базам данных
  • Инициализации серверных SDK
  • Настройки логгирования на сервере
  • Работы с файловой системой

Файлы с суффиксом .client.ts выполняются только в браузере:

plugins/analytics.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
}
}
}
})
plugins/toast.client.ts
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
})
})

Через Nuxt-плагины можно регистрировать любые Vue-плагины:

plugins/vue-query.ts
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
}
}
})
plugins/directives.ts
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)
}
})
})

Плагины могут быть асинхронными — Nuxt дождётся их инициализации перед рендерингом:

plugins/config.ts
export default defineNuxtPlugin(async (nuxtApp) => {
// Загрузка конфигурации перед стартом приложения
const config = await $fetch('/api/app-config')
return {
provide: {
appConfig: config
}
}
})

⚠️ Осторожно: асинхронные плагины блокируют рендеринг. Используйте их только когда данные действительно нужны перед первым рендером.


Плагины могут подписываться на жизненный цикл приложения:

plugins/lifecycle.ts
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 или другой сервис
})
})

<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>

📝 Практический пример: плагин авторизации

Заголовок раздела «📝 Практический пример: плагин авторизации»
plugins/auth.ts
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 }
}
})

tests/plugins/format.test.ts
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 события
Один плагин — одна задачаПринцип единственной ответственности