Nuxt Guide

Middleware

Named, global, and ordered route middleware conventions.

Types

TypeFilename patternRuns on
Namedauth.ts, admin.tsPages that declare it in definePageMeta
Globaltenant.global.tsEvery route navigation automatically
Ordered global01.org.tsEvery route — number controls execution order

Client-Only Guard

All middleware should be client-only in SPA apps. Add this guard at the top when middleware accesses cookies or browser APIs:

export default defineNuxtRouteMiddleware(() => {
  if (import.meta.server) return
  // ...
})

Named — auth.ts

Redirects unauthenticated users to /login. Used on all protected pages.

// app/middleware/auth.ts
export default defineNuxtRouteMiddleware(async (to) => {
  if (import.meta.server) return

  const publicRoutes = ['/login', '/register', '/']

  if (publicRoutes.includes(to.path)) return

  const accessToken = useCookie('accessToken')
  if (!accessToken?.value) return navigateTo('/login')
})

Applied in a page:

definePageMeta({ middleware: ['auth'] })

Named — admin.ts

Throws a 403 error if the current user is not an admin:

// app/middleware/admin.ts
export default defineNuxtRouteMiddleware(() => {
  if (import.meta.server) return

  const { currentUser } = useAuth()

  if (!currentUser.value || currentUser.value.type !== 'ADMIN') {
    throw createError({
      statusCode: 403,
      statusMessage: 'Access Denied: Admin privileges required',
    })
  }
})

Global — tenant.global.ts

Runs on every route. Resolves the tenant from subdomains and stores in global state. Sets a tenantNotFound flag if the org cannot be resolved.

// app/middleware/tenant.global.ts
export default defineNuxtRouteMiddleware(async () => {
  if (import.meta.server) return

  const tenant         = useTenant()
  const orgState       = useState<any | null>('currentOrganization', () => null)
  const tenantNotFound = useState<boolean>('tenantNotFound', () => false)

  // Already loaded — skip
  if (orgState.value) {
    tenantNotFound.value = false
    return
  }

  if (!tenant.value?.slug) {
    tenantNotFound.value = true
    return
  }

  try {
    const res         = await useOrganization().getByTenant()
    orgState.value    = res.organization
    tenantNotFound.value = false
  } catch (e: any) {
    if (e?.status === 404) tenantNotFound.value = true
  }
})

Ordered Global — 01.org.ts

Numeric prefix controls execution order (lower numbers run first). Validates IDs and loads org into state.

// app/middleware/01.org.ts
import { z } from 'zod'

const hexSchema = z.string().regex(/^[0-9a-fA-F]{24}$/, 'Invalid ID')

export default defineNuxtRouteMiddleware(async (to) => {
  if (import.meta.server) return

  const auth   = useAuth()
  const orgApi = useOrganization()
  const orgState = useState<any | null>('currentOrganization', () => null)

  if (!auth.currentUser.value) {
    await auth.getCurrentUser()
  }

  const orgId  = to.params.organization || auth.currentUser.value?.organizationId
  const parsed = hexSchema.safeParse(orgId)

  if (!parsed.success) return navigateTo('/organizations')

  const response = await orgApi.getById(orgId as string)
  orgState.value = response
})

Summary of Rules

  • Guard every middleware with if (import.meta.server) return.
  • Use navigateTo() to redirect; throw createError() for error pages.
  • Prefer named middleware over global unless the check truly needs to run on every route.
  • Use numeric prefixes (01., 02.) when multiple global middlewares need a defined execution order.