Vercel Logo

Route Protection

The /favorites and /visited pages should redirect unauthenticated visitors to login.

In Next.js, a root middleware.ts can inspect the session and use matcher configuration to select protected routes.

Nuxt can apply named middleware per page through definePageMeta. Each protected page declares the middleware it uses, while public pages remain unchanged.

Outcome

Create an auth middleware and apply it to the favorites and visited pages.

Fast Track

  1. Create app/middleware/auth.ts that checks useUserSession and redirects
  2. Add definePageMeta({ middleware: "auth" }) to the favorites and visited pages
  3. Verify unauthenticated users get redirected to login

Hands-on exercise 3.3

Build the middleware and protect the authenticated pages.

Requirements:

  1. Create app/middleware/auth.ts that redirects to /login if the user isn't logged in
  2. Add definePageMeta({ middleware: "auth" }) to app/pages/favorites.vue
  3. Add definePageMeta({ middleware: "auth" }) to app/pages/visited.vue
  4. Verify unauthenticated users get redirected when visiting either page

Implementation hints:

  • defineNuxtRouteMiddleware creates a named middleware. The filename becomes the name
  • useUserSession() works inside middleware because middleware runs in the Nuxt context
  • navigateTo("/login") returned from middleware triggers a redirect
  • definePageMeta is a compiler macro like defineProps. It runs at build time, not runtime

Here's the middleware:

app/middleware/auth.ts
export default defineNuxtRouteMiddleware((to) => {
  const { loggedIn } = useUserSession();
 
  if (!loggedIn.value) {
    return navigateTo("/login");
  }
});

The middleware checks loggedIn and redirects when it is false. The to parameter is available when protection depends on the target route.

In Next.js, the equivalent would be in middleware.ts:

// Next.js middleware.ts: for comparison
import { NextResponse } from "next/server";
 
export function middleware(request) {
  const session = request.cookies.get("session");
  if (!session) {
    return NextResponse.redirect(new URL("/login", request.url));
  }
}
 
export const config = {
  matcher: ["/favorites", "/visited"],
};

Next.js selects routes through matcher configuration. Nuxt places that selection in each page's metadata.

Now apply the middleware to the pages that need it. Add definePageMeta to the favorites page:

app/pages/favorites.vue
<script setup lang="ts">
definePageMeta({
  middleware: "auth",
});
</script>

And the visited page:

app/pages/visited.vue
<script setup lang="ts">
definePageMeta({
  middleware: "auth",
});
</script>

definePageMeta is a compiler macro, like defineProps. It gets extracted at build time and doesn't appear in the compiled component. The string "auth" matches the filename auth.ts in the middleware directory. Nuxt connects them automatically.

You can stack multiple middleware on a page with an array: middleware: ["auth", "admin"]. They run in order. If any middleware returns a redirect, the chain stops.

Global vs named middleware

Our auth.ts is a named middleware, applied per-page. If you want middleware that runs on EVERY page, create it in app/middleware/ with a .global.ts suffix, like auth.global.ts. We don't want that here because the browse page and login page should be public.

definePageMeta runs at build time

You can't use runtime variables inside definePageMeta. No definePageMeta({ middleware: someCondition ? "auth" : undefined }). The value must be static. If you need conditional middleware, put the condition inside the middleware itself.

Try It

  1. Log out if you're currently logged in
  2. Visit http://localhost:3000/favorites directly. You should be redirected to /login
  3. Visit http://localhost:3000/visited. It should also redirect to /login
  4. Visit http://localhost:3000/springs. No redirect. The browse page is still public
  5. Log in via GitHub. Visit /favorites and /visited. Both should load normally

Commit

git add -A && git commit -m "feat(auth): add route middleware to protect favorites and visited pages"

Done-When

  • app/middleware/auth.ts redirects unauthenticated users to /login
  • /favorites and /visited require authentication
  • /springs and /login remain public
  • You can explain the difference between named middleware and global middleware in Nuxt

Solution

app/middleware/auth.ts
export default defineNuxtRouteMiddleware((to) => {
  const { loggedIn } = useUserSession();
 
  if (!loggedIn.value) {
    return navigateTo("/login");
  }
});

Add definePageMeta to both protected pages:

app/pages/favorites.vue
<script setup lang="ts">
definePageMeta({
  middleware: "auth",
});
</script>
 
<template>
  <div>
    <h1>
      Your Favorites
    </h1>
    <p>
      Saved favorites will appear here once we add user features.
    </p>
  </div>
</template>
app/pages/visited.vue
<script setup lang="ts">
definePageMeta({
  middleware: "auth",
});
</script>
 
<template>
  <div>
    <h1>
      Visited Springs
    </h1>
    <p>
      Your visited springs will appear here once we add tracking.
    </p>
  </div>
</template>

Was this helpful?

supported.