10-nuxtjsTermsLevel_08Route 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:

  1. 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

  1. defineNuxtRouteMiddleware((to, from)) receives target route (to) and origin route (from).
  2. Returning navigateTo() performs HTTP 302 redirects on the server during SSR and SPA transitions on the client.
  3. 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:

  1. 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

  1. Inline middleware functions are scoped strictly to the page component where they are defined.
  2. Useful for single-use page guard logic without creating standalone files in middleware/.
  3. 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:

  1. 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

  1. Route middleware supports returning Promises or using async/await.
  2. Route navigation pauses until the async middleware Promise resolves.
  3. Prevents page rendering until session verification completes.


7. Key Takeaways

  • Route Middleware intercepts Vue Router navigation before the page renders.
  • It is located in the middleware/ directory (NOT server/middleware/).
  • Use return navigateTo() to safely redirect users.
  • Use return abortNavigation() to block navigation completely.
Built with LogoFlowershow