# Nuxt Middleware & Plugins
## When to Use
Working with `middleware/` or `plugins/` directories, route guards, app extensions.
## Route Middleware
Route middleware runs before navigation. Used for auth checks, redirects, logging.
### Global Middleware
Runs on every route change. **REQUIRED: Use `.global.ts` suffix:**
```ts
// middleware/auth.global.ts
export default defineNuxtRouteMiddleware((to, from) => {
const auth = useAuthStore()
if (to.meta.requiresAuth && !auth.isAuthenticated) {
return navigateTo('/login')
}
})
```
**Without `.global.ts` suffix, middleware is named (not global).**
## Red Flags - Stop and Check Skill
If you're thinking any of these, STOP and re-read this skill:
- "Suffix doesn't matter, it's about where I put it"
- "I'll redirect() instead of return navigateTo()"
- "I remember Nuxt 3 middleware patterns"
- "Export default function is simpler"
All of these mean: You're using outdated patterns. Use Nuxt 4 patterns instead.
### Named Middleware
Runs only when explicitly applied. No `.global` suffix:
```ts
// middleware/admin.ts
export default defineNuxtRouteMiddleware((to, from) => {
const auth = useAuthStore()
if (!auth.isAdmin) {
return navigateTo('/')
}
})
```
Apply in page:
```vue
```
### Middleware Return Values
```ts
export default defineNuxtRouteMiddleware((to, from) => {
// Allow navigation
return
// Redirect
return navigateTo('/login')
// Abort navigation
return abortNavigation()
// Abort with error
return abortNavigation('Not authorized')
})
```
### Middleware Order
1. Global middleware (alphabetical by filename)
2. Layout middleware (if layout defines middleware)
3. Page middleware (defined in definePageMeta)
## Plugins
Plugins extend Vue app with global functionality. Run during app initialization.
### Basic Plugin
```ts
// plugins/my-plugin.ts
export default defineNuxtPlugin((nuxtApp) => {
return {
provide: {
hello: (name: string) => `Hello ${name}!`
}
}
})
```
Use in components:
```vue
```
### Plugin with Vue Plugin
```ts
import type { PluginOptions } from 'vue-toastification'
// 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
} as PluginOptions)
})
```
### Plugin with Hooks
```ts
// plugins/init.ts
export default defineNuxtPlugin((nuxtApp) => {
nuxtApp.hook('app:created', () => {
console.log('App created')
})
nuxtApp.hook('page:finish', () => {
console.log('Page finished loading')
})
})
```
### Client-Only or Server-Only
Use file suffix:
- `.client.ts` - runs only on client
- `.server.ts` - runs only on server
```ts
// plugins/analytics.client.ts
export default defineNuxtPlugin(() => {
// Only runs in browser
if (window.analytics) {
window.analytics.init()
}
})
```
### Plugin Order
Use numeric prefix for execution order:
```
plugins/
├── 01.first.ts
├── 02.second.ts
└── 03.third.ts
```
### Async Plugins
```ts
// plugins/api.ts
export default defineNuxtPlugin(async (nuxtApp) => {
const config = await fetch('/api/config').then(r => r.json())
return {
provide: {
config
}
}
})
```
## Best Practices
**Middleware:**
- **Return navigation or nothing** - don't mutate state heavily
- **Keep logic minimal** - delegate to composables/stores
- **Use for guards & redirects** only
- **Check meta properly** - `to.meta.requiresAuth`
- **Global = `.global.ts`** suffix required
**Plugins:**
- **Use for app-wide functionality** only
- **Provide via `provide`** for type safety
- **Consider client/server context** - use `.client`/`.server`
- **Minimize work** in plugin initialization
- **Use hooks** for lifecycle events
## Common Mistakes
| ❌ Wrong | ✅ Right |
| ------------------------------------ | ------------------------------------------------------------ |
| `export default function({ route })` | `export default defineNuxtRouteMiddleware((to, from) => {})` |
| Mutate route object | Return navigateTo() or nothing |
| `middleware/auth.ts` (not global) | `middleware/auth.global.ts` (global) |
| `redirect('/login')` | `return navigateTo('/login')` |
| Plugin without defineNuxtPlugin | Wrap in defineNuxtPlugin() |
## Middleware Example: Auth
```ts
// middleware/auth.global.ts
export default defineNuxtRouteMiddleware((to, from) => {
const auth = useAuthStore()
// Public routes
const publicRoutes = ['/', '/login', '/register']
if (publicRoutes.includes(to.path)) {
return
}
// Check auth
if (!auth.isAuthenticated) {
return navigateTo('/login')
}
// Check role
if (to.meta.requiresAdmin && !auth.isAdmin) {
return abortNavigation('Access denied')
}
})
```
## Plugin Example: API Client
```ts
// plugins/api.ts
export default defineNuxtPlugin((nuxtApp) => {
const config = useRuntimeConfig()
const api = $fetch.create({
baseURL: config.public.apiBase,
onRequest({ request, options }) {
const auth = useAuthStore()
if (auth.token) {
options.headers = {
...options.headers,
Authorization: `Bearer ${auth.token}`
}
}
},
onResponseError({ response }) {
if (response.status === 401) {
navigateTo('/login')
}
}
})
return {
provide: {
api
}
}
})
```
## Resources
- Nuxt middleware: https://nuxt.com/docs/guide/directory-structure/middleware
- Nuxt plugins: https://nuxt.com/docs/guide/directory-structure/plugins
- Route middleware: https://nuxt.com/docs/getting-started/routing#route-middleware