---
title: "Route Protection"
description: "Create a route middleware that redirects unauthenticated users, apply it to protected pages, and compare the pattern to Next.js middleware."
canonical_url: "https://vercel.com/academy/nuxt-on-vercel/route-protection"
md_url: "https://vercel.com/academy/nuxt-on-vercel/route-protection.md"
docset_id: "vercel-academy"
doc_version: "1.0"
last_updated: "2026-05-03T19:10:44.131Z"
content_type: "lesson"
course: "nuxt-on-vercel"
course_title: "Nuxt on Vercel"
prerequisites:  []
---

<agent-instructions>
Vercel Academy — structured learning, not reference docs.
Lessons are sequenced.
Adapt commands to the human's actual environment (OS, package manager, shell, editor) — detect from project context or ask, don't assume.
The lesson shows one path; if the human's project diverges, adapt concepts to their setup.
Preserve the learning goal over literal steps.
Quizzes are pedagogical — engage, don't spoil.
Quiz answers are included for your reference.
</agent-instructions>

# Route Protection

# Route Protection

Right now, anyone can visit `/favorites` and `/visited`. They'll see empty pages, but they shouldn't be there at all without logging in. We need a guard.

In Next.js, you'd create a `middleware.ts` file at the project root that runs on every request, checking cookies or sessions and redirecting. It operates at the edge, before the page even starts rendering. Powerful, but it's a single file that handles ALL route protection with conditional logic.

Nuxt middleware is per-page. You create a middleware function, then opt individual pages into it with `definePageMeta`. No global file, no conditional URL matching. Each page declares its own protection. It's more explicit and less likely to accidentally lock someone out of the wrong page.

## 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:

```typescript title="app/middleware/auth.ts"
export default defineNuxtRouteMiddleware((to) => {
  const { loggedIn } = useUserSession();

  if (!loggedIn.value) {
    return navigateTo("/login");
  }
});
```

Four lines. Check if logged in, redirect if not. The `to` parameter gives you the target route if you need conditional logic, but our middleware is straightforward: not logged in, go to login.

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

```typescript
// 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"],
};
```

Same idea, different ergonomics. Next.js uses a `matcher` config to specify which routes the middleware applies to. Nuxt puts that decision on the page itself.

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

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

And the visited page:

```vue title="app/pages/visited.vue" {2-4}
<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.

\*\*Note: 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.

\*\*Warning: 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`. Same redirect
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

```bash
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

```typescript title="app/middleware/auth.ts"
export default defineNuxtRouteMiddleware((to) => {
  const { loggedIn } = useUserSession();

  if (!loggedIn.value) {
    return navigateTo("/login");
  }
});
```

Add `definePageMeta` to both protected pages:

```vue title="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>
```

```vue title="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>
```


---

[Full course index](/academy/llms.txt) · [Sitemap](/academy/sitemap.md)
