Back
Nuxt

Middleware and Plugins in Nuxt 4: Full Control

Learn to use middleware and plugins in Nuxt 4 to control navigation, authentication, and extend your application functionality.

Francisco ZapataWritten by Francisco Zapata
January 26, 202610 min read
Middleware and Plugins in Nuxt 4: Full Control

Middleware and plugins are two fundamental mechanisms in Nuxt 4 for intercepting and extending your application's behavior. Middleware controls navigation between routes, while plugins allow you to inject global functionality. Mastering both gives you full control over your application flow.

Route Middleware

Route middleware execute before navigating to a route. They are ideal for authentication, authorization, and redirects.

Types of Middleware

Nuxt 4 supports three types of route middleware:

1. Inline (Anonymous) Middleware

Defined directly in the page:

<script setup>
definePageMeta({
  middleware: [
    function (to, from) {
      const auth = useAuth()
      if (!auth.isAuthenticated.value) {
        return navigateTo('/login')
      }
    }
  ]
})
</script>

2. Named Middleware

Files in app/middleware/ assigned to specific pages:

// app/middleware/auth.ts
export default defineNuxtRouteMiddleware((to, from) => {
  const { isAuthenticated } = useAuth()

  if (!isAuthenticated.value) {
    return navigateTo('/login', {
      redirectCode: 302
    })
  }
})

Assignment in the page:

<script setup>
definePageMeta({
  middleware: ['auth']
})
</script>

3. Global Middleware

Files with .global.ts suffix that run on ALL routes:

// app/middleware/01.logger.global.ts
export default defineNuxtRouteMiddleware((to, from) => {
  console.log(`Navigating from ${from.path} to ${to.path}`)
})

Execution Order

1. Global middleware (sorted alphabetically by filename)

2. Page middleware (in the order defined in the array)

That is why it is useful to prefix with numbers: 01.logger.global.ts, 02.auth.global.ts.

Practical Examples

Role Middleware

// app/middleware/role.ts
export default defineNuxtRouteMiddleware((to) => {
  const { user } = useAuth()
  const requiredRole = to.meta.requiredRole as string

  if (requiredRole && user.value?.role !== requiredRole) {
    return navigateTo('/unauthorized')
  }
})
<script setup>
definePageMeta({
  middleware: ['auth', 'role'],
  requiredRole: 'admin'
})
</script>

Language Redirect Middleware

// app/middleware/i18n-redirect.global.ts
export default defineNuxtRouteMiddleware((to) => {
  const preferredLocale = useCookie('locale')

  if (!preferredLocale.value) {
    const browserLang = useRequestHeaders()['accept-language']
    preferredLocale.value = browserLang?.startsWith('es') ? 'es' : 'en'
  }
})

Maintenance Middleware

// app/middleware/maintenance.global.ts
export default defineNuxtRouteMiddleware((to) => {
  const config = useRuntimeConfig()

  if (config.public.maintenanceMode && to.path !== '/maintenance') {
    return navigateTo('/maintenance')
  }
})

Server Middleware

Server middleware intercepts HTTP requests in the Nitro server. They are different from route middleware:

// server/middleware/cors.ts
export default defineEventHandler((event) => {
  setResponseHeaders(event, {
    'Access-Control-Allow-Origin': '*',
    'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE',
    'Access-Control-Allow-Headers': 'Content-Type, Authorization'
  })

  if (getMethod(event) === 'OPTIONS') {
    setResponseStatus(event, 204)
    return ''
  }
})
// server/middleware/auth.ts
export default defineEventHandler(async (event) => {
  const url = getRequestURL(event)

  if (!url.pathname.startsWith('/api/protected')) return

  const token = getHeader(event, 'authorization')?.replace('Bearer ', '')

  if (!token) {
    throw createError({
      statusCode: 401,
      message: 'Authentication token required'
    })
  }

  const user = await verifyToken(token)
  event.context.user = user
})

Plugins

Plugins in Nuxt 4 allow you to extend application functionality, register global components, and provide helpers.

Creating Plugins

// app/plugins/analytics.client.ts
export default defineNuxtPlugin((nuxtApp) => {
  const analytics = {
    track(event: string, data?: Record<string, unknown>) {
      console.log('Track:', event, data)
    },
    page(name: string) {
      console.log('Page view:', name)
    }
  }

  nuxtApp.hook('page:finish', () => {
    analytics.page(useRoute().fullPath)
  })

  return {
    provide: {
      analytics
    }
  }
})

Usage in components:

<script setup>
const { $analytics } = useNuxtApp()

function handleClick() {
  $analytics.track('button_clicked', { section: 'hero' })
}
</script>

Plugin Suffixes

  • .client.ts: Only runs in the browser
  • .server.ts: Only runs on the server
  • No suffix: Runs in both environments

API Client Plugin

// app/plugins/api.ts
export default defineNuxtPlugin(() => {
  const config = useRuntimeConfig()

  const api = $fetch.create({
    baseURL: config.public.apiBase,
    onRequest({ options }) {
      const token = useCookie('auth_token')
      if (token.value) {
        options.headers = {
          ...options.headers,
          Authorization: `Bearer ${token.value}`
        }
      }
    },
    onResponseError({ response }) {
      if (response.status === 401) {
        navigateTo('/login')
      }
    }
  })

  return { provide: { api } }
})

Plugin Execution Order

Plugins execute in alphabetical order by default. To control the order, use the dependency system:

// app/plugins/02.auth.ts
export default defineNuxtPlugin({
  name: 'auth',
  dependsOn: ['api'],
  async setup(nuxtApp) {
    const { $api } = nuxtApp
  }
})

Or use numeric prefixes: 01.api.ts, 02.auth.ts.

Lifecycle Hooks

Both middleware and plugins can use Nuxt hooks:

export default defineNuxtPlugin((nuxtApp) => {
  nuxtApp.hook('app:created', () => {
    console.log('App created')
  })

  nuxtApp.hook('page:start', () => {
    console.log('Navigation started')
  })

  nuxtApp.hook('page:finish', () => {
    console.log('Navigation completed')
  })

  nuxtApp.hook('app:error', (error) => {
    console.error('App error:', error)
  })
})

Best Practices

1. Keep middleware lightweight: Avoid heavy logic that delays navigation.

2. Use named middleware for reusable logic, inline for page-specific logic.

3. Separate client/server plugins with appropriate suffixes.

4. Do not fetch in middleware: Use useFetch or useAsyncData in the page.

5. Handle errors: Always catch exceptions in plugins and middleware.

Conclusion

Middleware and plugins are complementary tools that give you full control over your Nuxt 4 application. Middleware protects and controls navigation, while plugins extend global functionality. Used correctly, they create a clean and maintainable architecture.

Comments (0)

Leave a comment

Be the first to comment