Middleware
Bob discovered that Alice could directly access the /admin admin panel without any permission checks. Charlie needs a route guard—one that automatically redirects unauthenticated users attempting to access the admin page to the login page, and prevents regular users from accessing admin routes. Nuxt 3's middleware system is the solution.
1. What You'll Learn
- Global middleware: middleware/auth.global.ts - Global scope and execution order
- Naming middleware: a combination of
definePageMetaand an array of middleware - Inline middleware: Lightweight middleware defined directly within a page
- Middleware and SSR: Dual Execution Mechanisms and Pure Client-Side Middleware
- MegaShop Administrator Route Guard + Role Check: A Practical Guide
2. A True Story of an Administrator
(1) Pain Point: A Critical Vulnerability Due to Lack of Permission Control
Alice typed /admin into her browser and accessed the MegaShop admin panel, where she could view all orders and user data. Bob, as an administrator, found that the interface he saw was exactly the same as that of a regular user, with no distinction whatsoever. Charlie urgently needed route-level access control.
(2) Solution for Routing Middleware
In Nuxt 3, middleware runs before a route transition and can redirect or block access:
// middleware/auth.ts
export default defineNuxtRouteMiddleware((to) => {
if (!isAuthenticated()) return navigateTo('/login')
})
(3) Benefits: Route-Level Access Control
Alice can no longer access /admin directly; if she is not logged in, she is automatically redirected to the login page. Bob's administrator privileges are enforced at the routing level.
3. Three Middleware Patterns
(1) Order of Middleware Execution
flowchart LR
A[Route Navigation] --> B[Global Middleware 1]
B --> C[Global Middleware 2]
C --> D[Named/Inline Middleware]
D --> E[Page Render]
D -->|abort| F[Redirect / Abort]
B -->|abort| F
C -->|abort| F
(2) Comparison of the Three Models
| Dimension | Global Middleware | Named Middleware | Inline Middleware |
|---|---|---|---|
| File Name | *.global.ts | *.ts (without "global") | No file |
| Scope | All Routes | Specific Pages | Current Page |
| Definition Method | middleware/ directory | middleware/ directory | within definePageMeta |
| Reusability | ✅ Automatic reuse | ✅ Multi-page references | ❌ Current page only |
| Use Cases | Global Authentication/Logging | Page-Specific Guard | Simple One-Time Check |
4. Global Middleware
(1) ▶ Example: Global Authentication Middleware
// middleware/auth.global.ts
export default defineNuxtRouteMiddleware((to, from) => {
// Skip auth for public pages
const publicPages = ['/', '/products', '/about', '/login', '/register']
if (publicPages.includes(to.path)) return
const token = useCookie('auth-token')
if (!token.value) {
return navigateTo(`/login?redirect=${to.path}`, { redirectCode: 302 })
}
})
Output:
// Execution Successful
(2) ▶ Example: Global Log Middleware
// middleware/log.global.ts
export default defineNuxtRouteMiddleware((to, from) => {
console.log(`Navigation: ${from.path} → ${to.path}`)
// No return = allow navigation
})
Output:
// Execution Successful
01-auth.global.ts runs before 02-log.global.ts. We recommend using a numeric prefix to control the order.
5. Naming Middleware
(1) ▶ Example: Administrator Role Middleware
// middleware/admin.ts
export default defineNuxtRouteMiddleware((to) => {
const user = useState('user')
if (!user.value) {
return navigateTo(`/login?redirect=${to.path}`)
}
if (user.value.role !== 'admin') {
// Regular user trying to access admin page
throw createError({
statusCode: 403,
message: 'Access denied. Admin role required.'
})
}
})
Output:
// Execution Successful
(2) ▶ Example: Referencing a named middleware on a page
<!-- pages/admin/index.vue -->
<template>
<div>
<h1>Admin Dashboard</h1>
<p>Welcome, Bob (Admin)</p>
</div>
</template>
<script setup lang="ts">
definePageMeta({
middleware: 'admin', // Reference named middleware
layout: 'sidebar'
})
</script>
Output:
// Execution Successful
(3) ▶ Example: Combining Multiple Middleware Components
<!-- pages/admin/settings.vue -->
<template>
<div>
<h1>Admin Settings</h1>
</div>
</template>
<script setup lang="ts">
definePageMeta({
middleware: ['auth', 'admin'], // Execute in order: auth → admin
layout: 'sidebar'
})
</script>
Output:
// Execution Successful
6. Inline Middleware
(1) ▶ Example: Defining middleware within a page
<!-- pages/checkout.vue -->
<template>
<div>
<h1>Checkout</h1>
<p>Cart total: ${{ total }} USD</p>
</div>
</template>
<script setup lang="ts">
definePageMeta({
middleware: defineNuxtRouteMiddleware((to) => {
// Inline: check if cart has items
const cart = useState<any[]>('cart')
if (cart.value.length === 0) {
return navigateTo('/cart')
}
})
})
const cart = useState<any[]>('cart')
const total = computed(() => cart.value.reduce((s, i) => s + i.price, 0))
</script>
Output:
// Execution Successful
(1) Choosing Between Inline and Named Middleware
| Scenario | Choice | Reason |
|---|---|---|
| Site-wide Authentication | Global | Check every route |
| Admin Routes | Names | Shared by Multiple Admin Pages |
| Check if shopping cart is not empty | Inline | Use only on the checkout page |
| VIP Area | Naming | May be used on multiple pages |
| A/B Testing Traffic Splitting | Inline | One-Time Logic |
7. Middleware and SSR
(1) SSR Dual Execution Mechanism
flowchart TB
A[User navigates to /admin] --> B[Server: middleware executes]
B -->|redirect| C[Server sends redirect response]
B -->|allow| D[Server renders page]
D --> E[Client: middleware executes again]
E -->|redirect| F[Client-side redirect]
E -->|allow| G[Client hydrates page]
(1) ▶ Example: Pure Client-Side Middleware
// middleware/client-only.ts
export default defineNuxtRouteMiddleware((to) => {
// Skip on server - only check on client
if (import.meta.server) return
const localStorage = window.localStorage
const preferences = JSON.parse(localStorage.getItem('user-preferences') || '{}')
if (preferences.theme === 'dark') {
// Client-only logic using browser APIs
document.documentElement.classList.add('dark')
}
})
Output:
// Execution Successful
(2) Considerations for SSR Middleware
| Problem | Cause | Solution |
|---|---|---|
| Middleware runs twice | SSR and client each run once | Expected behavior; ensure idempotency |
| Using window/localStorage | SSR (no browser environment) | if (import.meta.client) Check |
| Cookies are available in SSR | Requests carry cookies | Read them using useCookie |
| navigateTo in SSR | Returns a 302 redirect | Expected behavior |
| useState in middleware | ✅ Available | Suitable for checking login status |
8. Comprehensive Example: The MegaShop Permissions Guard System
// middleware/01-auth.global.ts - Global auth check
export default defineNuxtRouteMiddleware((to) => {
const publicPaths = ['/', '/products', '/products/**', '/about', '/login', '/register']
const isPublic = publicPaths.some(path => {
if (path.includes('')) return to.path.startsWith(path.replace('/', ''))
return path === to.path
})
if (isPublic) return
const token = useCookie('auth-token')
if (!token.value) {
return navigateTo(`/login?redirect=${encodeURIComponent(to.path)}`)
}
})
// middleware/admin.ts - Admin role check
export default defineNuxtRouteMiddleware((to) => {
const user = useCookie('user-data')
if (!user.value) {
return navigateTo('/login')
}
if ((user.value as any).role !== 'admin') {
throw createError({ statusCode: 403, message: 'Admin access required' })
}
})
<!-- pages/admin/products/index.vue -->
<template>
<div>
<h1>Product Management</h1>
<p>Manage 1 million products</p>
<!-- Admin product CRUD interface -->
</div>
</template>
<script setup lang="ts">
definePageMeta({
middleware: ['admin'],
layout: 'sidebar'
})
</script>
❓ FAQ
definePageMeta.defineNuxtRouteMiddleware itself does not support async. However, you can call synchronous APIs (such as useCookie or useState) within the middleware. If you need to perform asynchronous validation, it is recommended to handle it in a plugin or in the page's setup method.createError is thrown in the middleware?error.vue.useState or the payload property of NuxtApp. const nuxtApp = useNuxtApp() → nuxtApp.payload.xxx = data. The next middleware or page can access it.📖 Summary
- Three types of middleware: global (all routes), named (specific pages), and inline (current page)
- Global middleware is executed in alphabetical order by filename; it is recommended to use a numeric prefix to control the order.
- Multiple naming middleware components can be combined using the
middlewarearray reference indefinePageMeta. - SSR double execution is expected behavior; the browser API must be protected using
import.meta.client - MegaShop implements full permission guarding using global auth and a named admin
📝 Exercises
- Basic Exercise (Difficulty: ⭐): Create a middleware named
guest.tsthat only allows unauthenticated users to access the page (logged-in users are redirected to the home page), and apply it to the/loginpage. - Advanced Exercise (Difficulty ⭐⭐): Implement a complete authorization system: global auth checks for login + role checks for the "admin" user; the /admin/** route is protected.
- Challenge (Difficulty: ⭐⭐⭐): Implement data passing between middleware—retrieve user information in the
authmiddleware and store it in theNuxtApppayload, then read it directly in theadminmiddleware to avoid duplicate requests
---|



