How to use Vue Router guards
Vue Router guards enable control over navigation flow with hooks that run before, during, and after route transitions. As the creator of CoreUI with 12 years of Vue development experience, I’ve implemented router guards in production Vue applications that protect authenticated routes and manage complex navigation logic for millions of users.
The most secure approach combines global guards for authentication with per-route guards for role-based access control.
Global Before Guards
import { createRouter, createWebHistory } from 'vue-router'
const router = createRouter({
history: createWebHistory(),
routes: [
{ path: '/', component: Home },
{ path: '/login', component: Login },
{ path: '/dashboard', component: Dashboard, meta: { requiresAuth: true } },
{ path: '/admin', component: Admin, meta: { requiresAuth: true, role: 'admin' } }
]
})
router.beforeEach((to, from, next) => {
const isAuthenticated = localStorage.getItem('authToken')
const userRole = localStorage.getItem('userRole')
if (to.meta.requiresAuth && !isAuthenticated) {
next('/login')
} else if (to.meta.role && userRole !== to.meta.role) {
next('/')
} else {
next()
}
})
export default router
Per-Route Guards
const routes = [
{
path: '/profile',
component: Profile,
beforeEnter: (to, from, next) => {
if (isUserLoggedIn()) {
next()
} else {
next('/login')
}
}
},
{
path: '/admin',
component: Admin,
beforeEnter: (to, from, next) => {
if (hasAdminAccess()) {
next()
} else {
next({ name: 'Forbidden' })
}
}
}
]
Component Guards
<script>
export default {
name: 'EditPost',
beforeRouteEnter(to, from, next) {
fetchPost(to.params.id).then(post => {
next(vm => {
vm.post = post
})
})
},
beforeRouteUpdate(to, from, next) {
this.post = null
fetchPost(to.params.id).then(post => {
this.post = post
next()
})
},
beforeRouteLeave(to, from, next) {
if (this.hasUnsavedChanges) {
const answer = window.confirm('You have unsaved changes. Leave anyway?')
next(answer)
} else {
next()
}
}
}
</script>
Async Authentication Guard
import { useAuthStore } from '@/stores/auth'
router.beforeEach(async (to, from, next) => {
const authStore = useAuthStore()
if (to.meta.requiresAuth) {
if (!authStore.isAuthenticated) {
try {
await authStore.checkAuth()
next()
} catch (error) {
next({
path: '/login',
query: { redirect: to.fullPath }
})
}
} else {
next()
}
} else {
next()
}
})
Composition API Guards
<script setup>
import { onBeforeRouteLeave, onBeforeRouteUpdate } from 'vue-router'
import { ref } from 'vue'
const hasUnsavedChanges = ref(false)
onBeforeRouteLeave((to, from, next) => {
if (hasUnsavedChanges.value) {
const answer = window.confirm('Discard unsaved changes?')
next(answer)
} else {
next()
}
})
onBeforeRouteUpdate(async (to, from, next) => {
if (to.params.id !== from.params.id) {
await loadData(to.params.id)
}
next()
})
</script>
Global After Hooks
router.afterEach((to, from) => {
// Update page title
document.title = to.meta.title || 'My App'
// Track page view
if (window.gtag) {
window.gtag('config', 'GA_MEASUREMENT_ID', {
page_path: to.fullPath
})
}
// Scroll to top
window.scrollTo(0, 0)
})
Best Practice Note
This is the same navigation guard architecture we use in CoreUI’s Vue admin templates. Guards provide centralized control over route access and navigation flow. Always handle async operations properly in guards, use route meta fields for configuration, and implement proper error handling for failed navigations. Store sensitive auth checks server-side and use guards only for UI/UX improvements.
For production applications, consider using CoreUI’s Vue Admin Template which includes pre-configured router guards with authentication and role-based access control.
Related Articles
For complete routing implementation, check out how to use Vue Router and how to handle authentication in Vue.



