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
- Create
app/middleware/auth.tsthat checksuseUserSessionand redirects - Add
definePageMeta({ middleware: "auth" })to the favorites and visited pages - Verify unauthenticated users get redirected to login
Hands-on exercise 3.3
Build the middleware and protect the authenticated pages.
Requirements:
- Create
app/middleware/auth.tsthat redirects to/loginif the user isn't logged in - Add
definePageMeta({ middleware: "auth" })toapp/pages/favorites.vue - Add
definePageMeta({ middleware: "auth" })toapp/pages/visited.vue - Verify unauthenticated users get redirected when visiting either page
Implementation hints:
defineNuxtRouteMiddlewarecreates a named middleware. The filename becomes the nameuseUserSession()works inside middleware because middleware runs in the Nuxt contextnavigateTo("/login")returned from middleware triggers a redirectdefinePageMetais a compiler macro likedefineProps. It runs at build time, not runtime
Here's the middleware:
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:
// 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:
<script setup lang="ts">
definePageMeta({
middleware: "auth",
});
</script>And the visited page:
<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.
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.
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
- Log out if you're currently logged in
- Visit
http://localhost:3000/favoritesdirectly. You should be redirected to/login - Visit
http://localhost:3000/visited. Same redirect - Visit
http://localhost:3000/springs. No redirect. The browse page is still public - Log in via GitHub. Visit
/favoritesand/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.tsredirects unauthenticated users to/login/favoritesand/visitedrequire authentication/springsand/loginremain public- You can explain the difference between named middleware and global middleware in Nuxt
Solution
export default defineNuxtRouteMiddleware((to) => {
const { loggedIn } = useUserSession();
if (!loggedIn.value) {
return navigateTo("/login");
}
});Add definePageMeta to both protected pages:
<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><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?