Vue Router maps browser locations to component trees without requesting a new document for each navigation. RouterLink changes the location accessibly, RouterView renders the matched component, and route records describe paths, components, nested layouts, and metadata. The server must still return the application entry document for valid history-mode URLs.
After this lesson, you can define named and dynamic routes, read reactive route data, navigate programmatically, protect a route with a guard, and diagnose refresh-only 404 errors.
Vue Router is the official routing library for Vue.js. It enables client-side navigation in single-page applications - switching between views without a full page reload.
// router/index.js
import { createRouter, createWebHistory } from 'vue-router'
import Home from '../views/HomeView.vue'
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes: [
{
path: '/',
name: 'home',
component: Home
},
{
path: '/about',
name: 'about',
// Lazy loading - code split
component: () => import('../views/AboutView.vue')
},
{
// Dynamic route parameter
path: '/users/:id',
name: 'user-detail',
component: () => import('../views/UserDetail.vue'),
props: true // pass params as props
},
{
// Nested routes
path: '/dashboard',
component: () => import('../views/Dashboard.vue'),
meta: { requiresAuth: true },
children: [
{ path: '', name: 'dashboard', component: () => import('../views/DashboardHome.vue') },
{ path: 'settings', name: 'settings', component: () => import('../views/Settings.vue') },
]
},
{
// Redirect
path: '/home',
redirect: '/'
},
{
// 404 catch-all
path: '/:pathMatch(.*)*',
name: 'not-found',
component: () => import('../views/NotFound.vue')
}
]
})
// Navigation guard - protect routes
router.beforeEach((to, from, next) => {
const isLoggedIn = !!localStorage.getItem('token')
if (to.meta.requiresAuth && !isLoggedIn) {
next({ name: 'login', query: { redirect: to.fullPath } })
} else {
next()
}
})
export default router
<!-- App.vue -->
<template>
<nav>
<!-- RouterLink - declarative navigation -->
<RouterLink to="/">Home</RouterLink>
<RouterLink to="/about">About</RouterLink>
<!-- Named route -->
<RouterLink :to="{ name: 'user-detail', params: { id: 42 } }">
User 42
</RouterLink>
<!-- With query params -->
<RouterLink :to="{ path: '/search', query: { q: 'vue', page: 1 } }">
Search
</RouterLink>
<!-- Active class - automatically added when route matches -->
<RouterLink to="/about" active-class="nav-active" exact-active-class="nav-exact">
About
</RouterLink>
</nav>
<!-- RouterView - renders the matched component -->
<RouterView />
</template>
<!-- Using router composables -->
<template>
<div>
<p>User ID: {{ userId }}</p>
<p>Current path: {{ route.path }}</p>
<button @click="goBack">Back</button>
<button @click="goToDashboard">Dashboard</button>
</div>
</template>
<script setup>
import { computed } from 'vue'
import { useRoute, useRouter } from 'vue-router'
const route = useRoute() // current route info
const router = useRouter() // router instance
// Access route params
const userId = computed(() => route.params.id)
// Access query params
const searchQuery = computed(() => route.query.q)
// Programmatic navigation
function goBack() {
router.back()
}
function goToDashboard() {
router.push({ name: 'dashboard' })
}
function goToUser(id) {
router.push({ name: 'user-detail', params: { id } })
}
function replaceRoute() {
router.replace('/home') // replace current history entry
}
</script>
Give important routes names and navigate with route objects rather than assembling URL strings. A dynamic segment such as /users/:id places id in route.params. Treat every URL value as untrusted input: validate it before requesting data and show a clear not-found state when the record does not exist.
Nested routes let a parent layout remain visible while a child RouterView changes. The child path should represent the URL hierarchy intentionally; an absolute child path starts from the root even when its component renders inside the parent.
Use router.push for an application action that changes location and RouterLink for ordinary user navigation. Watch the specific route property that drives data, such as route.params.id, because the same component instance may be reused when only a parameter changes. Fetching only on mount can leave the old record on screen.
Global beforeEach guards are suitable for broad authentication policy. Route metadata can declare the requirement, while the guard decides whether to continue or redirect. Avoid performing every data fetch in a global guard; route components can own data that is specific to their screen and handle loading, errors, and cancellation close to the UI.
With HTML5 history mode, visiting /orders/42 directly asks the web server for that path. Configure the server to fall back to index.html for application routes while still returning real 404 responses for missing static assets or API paths. If navigation works by clicking but fails after refresh, this server rule is the first place to inspect.
Try this next
0 of 2 completed
Explore 500+ free tutorials across 20+ languages and frameworks.