Route Middleware
Route Middleware
Level 8 — Middleware & Plugins A system that allows you to run custom code before navigating to a specific Vue route. It is primarily used for frontend authentication, redirecting users, or validating route parameters.
1. Prerequisites
- Server Middleware — It is critical to understand that Route Middleware is fundamentally different from Server Middleware.
pages/Directory — The destinations that Route Middleware protects.
2. Term Category
Security & Middleware (Client & Server Route Guards): Route Middleware (defineNuxtRouteMiddleware) executes guard logic before entering a route, handling authentication, redirects, and permissions.
3. Explanation
Environment Context
- Server & Client
(1) Design Motivation — "Why did we design this?"
If you build a Dashboard page, you don't want unauthenticated users to see it. If you put the auth-checking logic directly inside the dashboard.vue component's onMounted hook, the page will briefly render, the user will see a flash of the dashboard, and then they will be redirected to the login page. This is insecure and looks terrible.
Route Middleware runs before the route change actually occurs. If the middleware detects the user isn't logged in, it cancels the navigation to the dashboard entirely and redirects them to /login before the dashboard ever renders.
(2) Server Middleware vs Route Middleware
This is the most confusing topic for Nuxt beginners.
- Server Middleware (
server/middleware/): Runs strictly on the Nitro Node.js backend. It intercepts HTTP requests (like fetching an image or hitting an API). It knows nothing about Vue. - Route Middleware (
middleware/): Runs inside the Vue application. It intercepts Vue Router navigation. When the user clicks a<NuxtLink>, Route Middleware runs in the browser. (During initial SSR, it runs on the server before Vue renders the HTML).
(3) Creating Route Middleware
You define Route Middleware inside the middleware/ directory using defineNuxtRouteMiddleware.
// middleware/auth.ts
export default defineNuxtRouteMiddleware((to, from) => {
// `to` is the route the user is trying to visit
// `from` is the route they are coming from
const user = useCookie('auth_token');
// If there's no token, redirect to login
if (!user.value) {
return navigateTo('/login');
}
});
To apply it to a page, use definePageMeta:
<!-- pages/dashboard.vue -->
<script setup lang="ts">
definePageMeta({
middleware: 'auth' // Refers to middleware/auth.ts
});
</script>
<template>
<h1>Secret Dashboard</h1>
</template>
4. Common Mistakes & Pitfalls
Mistake 1: Using useRouter().push() inside Middleware
The mistake: Trying to redirect the user by calling the standard Vue router push method inside a middleware function.
Why it's wrong: Nuxt's Route Middleware is designed to work seamlessly across both the Node.js Server (during SSR) and the Browser. useRouter().push() only works in the browser. If the middleware triggers on the server, your app will crash.
Golden Rule: ALWAYS use return navigateTo('/path') inside middleware. It safely handles redirects on both the server (sending a 302 HTTP status) and the client (triggering vue-router).
Incorrect:
export default defineNuxtRouteMiddleware((to, from) => {
const router = useRouter();
router.push('/login');
});
Fix:
export default defineNuxtRouteMiddleware((to, from) => {
return navigateTo('/login');
});
Mistake 2: Calling Asynchronous Fetching Composables in Middleware Without Proper await
The mistake: Calling $fetch('/api/check') inside defineNuxtRouteMiddleware without await.
Why it's wrong: If asynchronous checks are not awaited inside route middleware, the router will complete the page transition before authentication status is verified.
Incorrect:
export default defineNuxtRouteMiddleware((to) => {
$fetch('/api/check').then(res => { if (!res) navigateTo('/login'); }); // ❌ Un-awaited promise!
});
Fix:
export default defineNuxtRouteMiddleware(async (to) => {
const res = await $fetch('/api/check'); // Await async checks
if (!res) return navigateTo('/login');
});
Mistake 3: Creating Infinite Redirect Loops in Route Middleware
The mistake: Writing export default defineNuxtRouteMiddleware((to) => { if (!isAuth) return navigateTo('/login'); }) without checking if (to.path === '/login').
Why it's wrong: If unauthenticated user visits /login, the middleware redirects them to /login again, creating an infinite redirect loop. Always guard target path.
Incorrect:
export default defineNuxtRouteMiddleware((to) => {
if (!isAuth) return navigateTo('/login'); // ❌ Infinite loop when to.path is already /login!
});
Fix:
export default defineNuxtRouteMiddleware((to) => {
if (!isAuth && to.path !== '/login') return navigateTo('/login'); // Path guard check
});
5. Practice Exercises
Exercise 1: Redirecting Unauthorized Users with navigateTo()
Scenario:
Write route middleware redirecting unauthenticated users to /login?redirect=....
Requirements:
- Use
navigateTo("/login?redirect=...")inside route middleware.
Answer
Implementation
// middleware/auth.ts
export default defineNuxtRouteMiddleware((to) => {
const user = useUser();
if (!user.value) {
return navigateTo({
path: "/login",
query: { redirect: to.fullPath }
});
}
});
Technical Explanation
defineNuxtRouteMiddleware((to, from))receives target route (to) and origin route (from).- Returning
navigateTo()performs HTTP 302 redirects on the server during SSR and SPA transitions on the client. - Preserves original target route path in URL query parameters for post-login redirection.
Exercise 2: Implementing Anonymous Inline Route Middleware
Scenario:
Define an anonymous inline middleware function directly inside definePageMeta() in a page component.
Requirements:
- Define inline function in
definePageMeta({ middleware: [...] }).
Answer
Implementation
<!-- pages/special.vue -->
<script setup lang="ts">
definePageMeta({
middleware: [
(to, from) => {
console.log("Executing inline middleware for special page");
if (to.query.secret !== "true") {
return navigateTo("/");
}
}
]
});
</script>
<template>
<div>
<h1>Secret Special Page</h1>
</div>
</template>
Technical Explanation
- Inline middleware functions are scoped strictly to the page component where they are defined.
- Useful for single-use page guard logic without creating standalone files in
middleware/. - Compact page guard pattern.
Exercise 3: Asynchronous Route Middleware Execution
Scenario: Execute async token validation inside route middleware before granting access to protected routes.
Requirements:
- Use
async (to) => { await $fetch(...) }.
Answer
Implementation
// middleware/verify-session.ts
export default defineNuxtRouteMiddleware(async (to) => {
const token = useCookie("token");
if (token.value) {
try {
// Asynchronously validate session token with backend API
await $fetch("/api/auth/validate", {
headers: { Authorization: `Bearer ${token.value}` }
});
} catch (err) {
token.value = null;
return navigateTo("/login");
}
}
});
Technical Explanation
- Route middleware supports returning Promises or using
async/await. - Route navigation pauses until the async middleware Promise resolves.
- Prevents page rendering until session verification completes.
6. Related Terms
- Global vs Named Middleware — The two ways to apply these functions.
pages/Directory — Where middleware is applied viadefinePageMeta.definePageMetaCompiler Macro — Related concept:definePageMetaCompiler Macro.useRoute&useRouterHooks — Related concept:useRoute&useRouterHooks.- Server Middleware — Related concept: Server Middleware.
abortNavigationUtility — Related concept:abortNavigationUtility.
7. Key Takeaways
- Route Middleware intercepts Vue Router navigation before the page renders.
- It is located in the
middleware/directory (NOTserver/middleware/). - Use
return navigateTo()to safely redirect users. - Use
return abortNavigation()to block navigation completely.