Most real-world Vue applications are single-page applications (SPAs): the browser loads one HTML page, and JavaScript swaps the visible content as the user moves around. Vue Router is the official routing library for Vue 3. It maps URLs to components, keeps the address bar in sync, and gives you tools like dynamic parameters, navigation guards, and lazy loading. In this lesson you'll install Vue Router, define routes, link between pages, and protect routes that require authentication.
For Vue 3 you need Vue Router version 4:
npm install vue-router@4If you created your project with npm create vue@latest and answered yes to the router question, this is already set up for you.
Create a router file that lists every route in your app. Each route pairs a URL path with the component that should render there:
// src/router/index.js
import { createRouter, createWebHistory } from 'vue-router'
import HomeView from '../views/HomeView.vue'
import AboutView from '../views/AboutView.vue'
const routes = [
{ path: '/', name: 'home', component: HomeView },
{ path: '/about', name: 'about', component: AboutView }
]
const router = createRouter({
history: createWebHistory(),
routes
})
export default routerThen register the router when you create the app:
// src/main.js
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
createApp(App).use(router).mount('#app')createWebHistory gives you clean URLs like /about. The alternative, createWebHashHistory, produces /#/about and works without any server configuration, which can be handy on static hosts.
Two components do the visible work. <RouterView> is the outlet where the matched component renders, and <RouterLink> creates navigation links that update the URL without a full page reload:
<template>
<nav>
<RouterLink to='/'>Home</RouterLink>
<RouterLink to='/about'>About</RouterLink>
</nav>
<main>
<RouterView />
</main>
</template>Vue Router automatically adds an router-link-active class to the link matching the current URL, which makes styling the active menu item easy.
Real apps need URLs like /users/42. Declare a parameter with a colon, then read it inside the component with the useRoute composable:
// route definition
{ path: '/users/:id', name: 'user', component: UserView }// UserView.vue
import { useRoute } from 'vue-router'
const route = useRoute()
console.log(route.params.id) // '42' for /users/42Sometimes you navigate from code, for example after a form submits successfully. Use the useRouter composable:
import { useRouter } from 'vue-router'
const router = useRouter()
function onSaved(newId) {
router.push({ name: 'user', params: { id: newId } })
}router.push adds a history entry, router.replace swaps the current one, and router.back() behaves like the browser back button.
Guards run before a navigation completes, which is perfect for authentication checks:
router.beforeEach((to) => {
const loggedIn = Boolean(localStorage.getItem('token'))
if (to.meta.requiresAuth && !loggedIn) {
return { name: 'login' }
}
})Mark protected routes with meta: { requiresAuth: true } and the guard redirects anonymous visitors to the login page.
Large apps should not ship every page in one bundle. Pass a dynamic import instead of a component and Vue Router loads that chunk only when the route is visited:
{ path: '/reports', component: () => import('../views/ReportsView.vue') }<RouterView> renders the matched component and <RouterLink> handles navigation without reloads.:id become available via useRoute().params.useRouter().push() for navigation from code and beforeEach guards to protect routes.Next up: as apps grow, passing props between distant components gets painful. In the next lesson we solve that with State Management with Pinia.