Navigation guards
A navigation guard is a function the router calls before every navigation. It can allow the navigation, cancel it or redirect to another page. This is how the blog admin section is closed to people who have not logged in.
A logged-in flag
A simple composable with shared state is enough for the example. Create src/composables/useAuth.js:
// src/composables/useAuth.js
import { ref } from 'vue'
const isLoggedIn = ref(false)
export function useAuth() {
function login() {
isLoggedIn.value = true
}
function logout() {
isLoggedIn.value = false
}
return { isLoggedIn, login, logout }
}The ref is declared outside the function, so all components and the router see the same value. A real app would make a request to the server here.
Marking routes
A route's meta field can hold any data of your own. Mark the /admin section and add a login page in src/router/index.js:
// src/router/index.js, part of the routes array
const routes = [
{
path: '/admin',
component: () => import('../views/AdminLayout.vue'),
meta: { requiresAuth: true },
children: [
{ path: '', name: 'admin-posts', component: () => import('../views/AdminPostsView.vue') },
],
},
{ path: '/login', name: 'login', component: () => import('../views/LoginView.vue') },
]Child routes inherit the parent's meta mark, so /admin/new is closed too.
The beforeEach guard
After creating the router, before export default router, add the check:
// src/router/index.js
import { useAuth } from '../composables/useAuth.js'
router.beforeEach((to) => {
const { isLoggedIn } = useAuth()
if (to.meta.requiresAuth && !isLoggedIn.value) {
return { name: 'login', query: { redirect: to.fullPath } }
}
})to is the route being navigated to. If the function returns an address, the router redirects there; if it returns nothing, the navigation goes ahead.
| Return value | Result |
|---|---|
Nothing or true | Navigation allowed |
false | Navigation cancelled, the user stays put |
| An address or a route object | Redirect |
The login page
<!-- src/views/LoginView.vue -->
<script setup>
import { useRoute, useRouter } from 'vue-router'
import { useAuth } from '../composables/useAuth.js'
const route = useRoute()
const router = useRouter()
const { login } = useAuth()
function enter() {
login()
router.push(route.query.redirect || '/')
}
</script>
<template>
<h1>Log in</h1>
<button @click="enter">Log in as Anna</button>
</template>Open http://localhost:5173/admin — the address changes to /login?redirect=/admin. After pressing "Log in as Anna" the admin section opens.
A guard on one route
A check needed by only one route is written right in it, in the beforeEnter field. For example, allowing the post page only with a numeric id:
// src/router/index.js, the post route
const postRoute = {
path: '/posts/:id',
name: 'post',
component: () => import('../views/PostView.vue'),
beforeEnter: (to) => {
if (!/^\d+$/.test(to.params.id)) {
return { name: 'home' }
}
},
}The address /posts/abc redirects to the home page, and /posts/5 opens the post.
Common mistake
Redirecting to the login page without checking where the navigation goes: return { name: 'login' } for every route. Navigating to /login also passes through beforeEach, is redirected to /login again, and the router reports an infinite redirect error. Redirect only when the condition holds, as with to.meta.requiresAuth above.