Table of Contents
- What is Vue Router?
- Setting Up Vue Router
- Core Concepts
- Routes and Route Configuration
router-view: The Content Outletrouter-link: Navigation Links
- Dynamic Routes: Handling Parameters
- Nested Routes: Building Complex UIs
- Route Guards: Controlling Navigation
- Programmatic Navigation
- Route Meta Fields: Adding Metadata
- Lazy Loading Routes: Boosting Performance
- Advanced Features
- History Mode vs. Hash Mode
- Scroll Behavior
- Named Routes and Views
- Route Transitions
- Best Practices
- Conclusion
- References
What is Vue Router?
Vue Router is the official routing library for Vue.js, maintained by the Vue team. It enables SPAs by synchronizing the application’s UI with the browser’s URL. Key features include:
- Declarative Routing: Define routes in a configuration file, mapping URLs to Vue components.
- Dynamic Routing: Handle variable URL parameters (e.g.,
/user/:id). - Nested Routes: Create complex layouts with nested component hierarchies.
- Navigation Control: Use route guards to restrict access to routes (e.g., authentication checks).
- History Management: Integrate with the browser’s history API for back/forward navigation.
Without Vue Router, SPAs would lack URL-driven navigation, making it hard for users to bookmark pages, use the browser’s back button, or share links—critical for usability and SEO.
Setting Up Vue Router
Let’s walk through installing and configuring Vue Router in a Vue 3 project.
Prerequisites
- A Vue 3 project (created with
vue createor Vite).
Step 1: Install Vue Router
Use npm or yarn to install the latest version (Vue Router 4+ for Vue 3):
npm install vue-router@4
# or
yarn add vue-router@4
Step 2: Create a Router Instance
Create a router directory in your project, and add an index.js file to define routes:
// src/router/index.js
import { createRouter, createWebHistory } from 'vue-router'
import Home from '../views/Home.vue'
import About from '../views/About.vue'
// Define routes
const routes = [
{
path: '/',
name: 'Home',
component: Home
},
{
path: '/about',
name: 'About',
component: About
}
]
// Create router instance
const router = createRouter({
history: createWebHistory(), // Uses HTML5 history mode (clean URLs)
routes // Short for `routes: routes`
})
export default router
Step 3: Integrate Router with Vue App
Import the router in your root main.js and pass it to the Vue app:
// src/main.js
import { createApp } from 'vue'
import App from './App.vue'
import router from './router' // Import the router
createApp(App)
.use(router) // Install the router
.mount('#app')
Step 4: Add Router View and Links
In your root App.vue, add <router-view> (where routed components render) and <router-link> (navigation links):
<!-- src/App.vue -->
<template>
<div id="app">
<!-- Navigation Links -->
<nav>
<router-link to="/">Home</router-link> |
<router-link to="/about">About</router-link>
</nav>
<!-- Route Outlet: Renders matched component -->
<router-view />
</div>
</template>
Run your app—you’ll now see navigation links that switch between Home and About components without page reloads!
Core Concepts
Routes and Route Configuration
A “route” is a mapping between a URL path and a Vue component. The routes array in your router config defines these mappings. Each route object has:
path: The URL path (e.g.,/about).name: A unique identifier for the route (optional but recommended).component: The Vue component to render when the path matches.
router-view: The Content Outlet
<router-view> is a special component that acts as a placeholder for the currently matched route’s component. Think of it as a “dynamic slot” that updates when the URL changes.
Example: If the URL is /about, <router-view> renders the About component.
router-link: Navigation Links
<router-link> is Vue Router’s replacement for <a> tags. It generates an <a> element and automatically handles navigation without full page reloads.
Key props:
to: The target route (path or named route).active-class: Custom class applied when the link is active (default:router-link-active).
Example:
<router-link to="/about" active-class="active">About</router-link>
When the URL is /about, the link will have the active class for styling.
Dynamic Routes: Handling Parameters
Often, you need to handle URLs with variable data, like /user/123 (where 123 is a user ID). Vue Router supports dynamic segments with :parameterName.
Define a Dynamic Route
Add a dynamic segment to your route config:
// src/router/index.js
const routes = [
// ...
{
path: '/user/:id', // `:id` is the dynamic parameter
name: 'User',
component: () => import('../views/User.vue') // Lazy-loaded (see later section)
}
]
Access Route Parameters
In the User component, access the id parameter using the useRoute composable (Vue 3’s <script setup> syntax):
<!-- src/views/User.vue -->
<template>
<h1>User Profile: {{ $route.params.id }}</h1>
</template>
<script setup>
import { useRoute } from 'vue-router'
const route = useRoute()
console.log(route.params.id) // Logs the dynamic ID
</script>
Reacting to Parameter Changes
When navigating from /user/123 to /user/456, the User component reuses (instead of re-mounting). To react to parameter changes:
-
Watch the
$routeobject:import { watch } from 'vue' watch( () => route.params.id, (newId, oldId) => { // Fetch data for newId } ) -
Use the
beforeRouteUpdateguard (in-component guard):import { onBeforeRouteUpdate } from 'vue-router' onBeforeRouteUpdate((to, from) => { console.log('ID changed from', from.params.id, 'to', to.params.id) })
Nested Routes
Nested routes let you render components inside other components, ideal for layouts like dashboards with sub-pages (e.g., /dashboard/profile).
Step 1: Define Nested Routes
Add a children array to a route to define nested routes:
// src/router/index.js
const routes = [
{
path: '/dashboard',
name: 'Dashboard',
component: () => import('../views/Dashboard.vue'),
children: [
{
path: 'profile', // Matches `/dashboard/profile`
name: 'DashboardProfile',
component: () => import('../views/DashboardProfile.vue')
},
{
path: 'settings', // Matches `/dashboard/settings`
name: 'DashboardSettings',
component: () => import('../views/DashboardSettings.vue')
}
]
}
]
Step 2: Add Nested router-view
In the parent component (Dashboard.vue), add a <router-view> to render child routes:
<!-- src/views/Dashboard.vue -->
<template>
<div class="dashboard">
<h1>Dashboard</h1>
<nav>
<router-link to="/dashboard/profile">Profile</router-link> |
<router-link to="/dashboard/settings">Settings</router-link>
</nav>
<!-- Child routes render here -->
<router-view />
</div>
</template>
Now, navigating to /dashboard/profile will render DashboardProfile.vue inside Dashboard.vue.
Route Guards: Controlling Navigation
Route guards let you control access to routes (e.g., “only authenticated users can visit /dashboard”). There are three types of guards:
1. Global Guards
Run for every navigation. Use router.beforeEach to check conditions before navigation:
// src/router/index.js
router.beforeEach((to, from) => {
// Check if route requires authentication
if (to.meta.requiresAuth && !isAuthenticated()) {
// Redirect to login if unauthenticated
return { name: 'Login' }
}
})
Here, to is the target route, from is the current route, and the guard returns a redirect route if navigation should be blocked.
2. Per-Route Guards
Defined directly in a route’s config with beforeEnter:
{
path: '/admin',
name: 'Admin',
component: Admin,
beforeEnter: (to, from) => {
// Only allow admins
if (!isAdmin()) {
return { name: 'Home' }
}
}
}
3. In-Component Guards
Defined inside a component to react to route changes for that component. Common guards:
beforeRouteEnter: Runs before the component is created (usenextto access the instance).beforeRouteUpdate: Runs when the route changes but the component is reused (e.g., dynamic parameter updates).beforeRouteLeave: Runs when leaving the component (e.g., confirm unsaved changes).
Example:
<script setup>
import { onBeforeRouteLeave } from 'vue-router'
onBeforeRouteLeave((to, from) => {
const answer = confirm('Are you sure you want to leave? Unsaved changes will be lost.')
if (!answer) return false // Cancel navigation
})
</script>
Programmatic Navigation
Instead of using <router-link>, you can navigate programmatically with the useRouter composable:
<script setup>
import { useRouter } from 'vue-router'
const router = useRouter()
const goToAbout = () => {
router.push('/about') // Navigate to path
// Or use named route (better for maintainability)
router.push({ name: 'About' })
}
const goToUser = (userId) => {
router.push({ name: 'User', params: { id: userId } }) // Navigate with params
}
const goBack = () => {
router.go(-1) // Go back one page (like browser's back button)
}
</script>
Route Meta Fields: Adding Metadata
Attach custom metadata to routes with the meta field. Use it to share logic across routes (e.g., “requires auth” flags):
// src/router/index.js
const routes = [
{
path: '/dashboard',
name: 'Dashboard',
component: Dashboard,
meta: { requiresAuth: true, role: 'user' } // Custom metadata
},
{
path: '/admin',
name: 'Admin',
component: Admin,
meta: { requiresAuth: true, role: 'admin' }
}
]
Access meta in guards or components:
// In a global guard
router.beforeEach((to) => {
if (to.meta.requiresAuth) {
if (to.meta.role === 'admin' && !isAdmin()) {
return { name: 'Home' }
}
}
})
Lazy Loading Routes: Boosting Performance
Lazy loading delays loading non-critical components until they’re needed, reducing initial bundle size and improving load times. Use dynamic import() instead of static imports:
// src/router/index.js
const routes = [
{
path: '/about',
name: 'About',
// Lazy load: component is loaded only when route is visited
component: () => import('../views/About.vue')
}
]
Webpack or Vite will split the About component into a separate chunk, loaded on demand.
Advanced Features
History Mode vs. Hash Mode
Vue Router supports two history modes:
createWebHistory()(HTML5 History Mode): Uses clean URLs (e.g.,/about) but requires server configuration to redirect all requests toindex.html(to handle client-side routing).createWebHashHistory()(Hash Mode): Uses URLs with#(e.g.,/#/about). No server config needed, but less SEO-friendly.
For production SPAs, use createWebHistory with server setup (e.g., Nginx or Netlify redirects).
Scroll Behavior
Control scrolling when navigating (e.g., “scroll to top” for new routes). Configure in the router:
const router = createRouter({
history: createWebHistory(),
routes,
scrollBehavior(to, from, savedPosition) {
// Scroll to top for new routes
if (savedPosition) {
return savedPosition // Restore position for back/forward navigation
} else {
return { top: 0 } // Scroll to top
}
}
})
Named Views
Render multiple components in parallel using named router-views. Useful for layouts with sidebars and main content:
// Route config
{
path: '/',
components: {
default: Home, // Renders in <router-view>
sidebar: Sidebar // Renders in <router-view name="sidebar">
}
}
In the template:
<router-view /> <!-- Renders Home -->
<router-view name="sidebar" /> <!-- Renders Sidebar -->
Route Transitions
Animate route changes with Vue’s <transition> component. Wrap <router-view>:
<template>
<transition name="fade" mode="out-in">
<router-view />
</transition>
</template>
<style>
.fade-enter-active, .fade-leave-active {
transition: opacity 0.3s;
}
.fade-enter-from, .fade-leave-to {
opacity: 0;
}
</style>
Best Practices
- Use Named Routes: Reference routes by
nameinstead of hardcoding paths (e.g.,router.push({ name: 'User' })). Easier to update paths later. - Lazy Load Routes: Split code into chunks to improve initial load time.
- Organize Route Config: For large apps, split routes into separate files (e.g.,
routes/auth.js,routes/dashboard.js) and import them. - Handle 404s: Add a catch-all route for undefined paths:
{ path: '/:pathMatch(.*)*', name: 'NotFound', component: NotFound } - Validate Route Params: Use libraries like
zodto validate dynamic parameters (e.g., ensureidis a number).
Conclusion
Vue Router is a powerful tool for building SPAs, enabling URL-driven navigation, dynamic routing, and fine-grained control over user flows. By mastering its core concepts (routes, dynamic parameters, guards) and advanced features (lazy loading, meta fields), you can create intuitive, performant apps that feel native to the web.
Start small with basic routes, then layer in nested routes and guards as your app grows. Refer to the official docs for deeper dives into edge cases and advanced patterns!