Files
inkreach-official-website/apps/website/.agents/skills/nuxt/references/middleware-plugins.md
T
yeuimu 6c61a4e871 feat(deploy): production deployment setup and fixes
- Debian-based api image (bookworm-slim), docker/debian mirrors, prisma
  binaryTargets for openssl 3.0
- nginx: admin SPA under /admin, TLS via acme.sh (ZeroSSL) + auto-renewal
  cron, http->https redirect
- prisma: add origin_goods.delisted migration, sync missing schema
  (good_image/tag_font_color/good_tags), fix users.createdAt Timestamptz
- api: CORS wildcard reflection, helmet CORP cross-origin, price
  backfill in persistProductDetail, categoryIcon ancestor fallback,
  mediaByColor per-color gallery in public goods detail
- admin: /admin base path (vite + router)
- import-data.mjs: udt_name casting, serial sequence advance fix
2026-08-26 14:23:09 +08:00

6.3 KiB

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:

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

// middleware/admin.ts
export default defineNuxtRouteMiddleware((to, from) => {
  const auth = useAuthStore()

  if (!auth.isAdmin) {
    return navigateTo('/')
  }
})

Apply in page:

<script setup lang="ts">
definePageMeta({
  middleware: ['admin']
})
</script>

Middleware Return Values

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

// plugins/my-plugin.ts
export default defineNuxtPlugin((nuxtApp) => {
  return {
    provide: {
      hello: (name: string) => `Hello ${name}!`
    }
  }
})

Use in components:

<script setup lang="ts">
const { $hello } = useNuxtApp()
console.log($hello('World')) // "Hello World!"
</script>

Plugin with Vue Plugin

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

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

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

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

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