Appearance
Vue Router
Vue Router is the official routing library for Vue.js. It enables navigation between pages in single-page applications (SPAs). In this tutorial, you'll learn how to set up and use Vue Router.
Installation
bash
npm install vue-router@4Basic Setup
js
// src/router/index.js
import { createRouter, createWebHistory } from 'vue-router'
import Home from '@/views/Home.vue'
import About from '@/views/About.vue'
const routes = [
{
path: '/',
name: 'home',
component: Home
},
{
path: '/about',
name: 'about',
component: About
}
]
const router = createRouter({
history: createWebHistory(),
routes
})
export default routerts
// src/router/index.ts
import { createRouter, createWebHistory, RouteRecordRaw } from 'vue-router'
import Home from '@/views/Home.vue'
import About from '@/views/About.vue'
const routes: RouteRecordRaw[] = [
{
path: '/',
name: 'home',
component: Home
},
{
path: '/about',
name: 'about',
component: About
}
]
const router = createRouter({
history: createWebHistory(),
routes
})
export default routerRegister in main:
js
// src/main.js
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
createApp(App)
.use(router)
.mount('#app')ts
// src/main.ts
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
createApp(App)
.use(router)
.mount('#app')Navigation Components
RouterLink and RouterView
vue
<!-- src/App.vue -->
<script setup>
// Router components are globally available
</script>
<template>
<div id="app">
<nav>
<!-- RouterLink for navigation -->
<RouterLink to="/">Home</RouterLink>
<RouterLink to="/about">About</RouterLink>
<RouterLink :to="{ name: 'about' }">About (by name)</RouterLink>
</nav>
<!-- RouterView renders the matched component -->
<RouterView />
</div>
</template>
<style>
nav {
padding: 20px;
}
nav a {
margin-right: 15px;
}
/* Active link styling */
nav a.router-link-active {
color: #42b883;
font-weight: bold;
}
</style>vue
<!-- src/App.vue -->
<script setup lang="ts">
// Router components are globally available
</script>
<template>
<div id="app">
<nav>
<!-- RouterLink for navigation -->
<RouterLink to="/">Home</RouterLink>
<RouterLink to="/about">About</RouterLink>
<RouterLink :to="{ name: 'about' }">About (by name)</RouterLink>
</nav>
<!-- RouterView renders the matched component -->
<RouterView />
</div>
</template>
<style>
nav {
padding: 20px;
}
nav a {
margin-right: 15px;
}
/* Active link styling */
nav a.router-link-active {
color: #42b883;
font-weight: bold;
}
</style>Dynamic Routes
Route Parameters
js
const routes = [
{
path: '/users/:id',
name: 'user',
component: () => import('@/views/User.vue')
},
{
path: '/posts/:category/:id',
name: 'post',
component: () => import('@/views/Post.vue')
}
]ts
const routes: RouteRecordRaw[] = [
{
path: '/users/:id',
name: 'user',
component: () => import('@/views/User.vue')
},
{
path: '/posts/:category/:id',
name: 'post',
component: () => import('@/views/Post.vue')
}
]Accessing Parameters
vue
<!-- src/views/User.vue -->
<script setup>
import { useRoute } from 'vue-router'
import { ref, watch } from 'vue'
const route = useRoute()
// Access route params
const userId = ref(route.params.id)
// Watch for param changes
watch(
() => route.params.id,
(newId) => {
userId.value = newId
// Fetch new user data
}
)
</script>
<template>
<div>
<h1>User {{ userId }}</h1>
<p>Full path: {{ route.fullPath }}</p>
<p>Query: {{ route.query }}</p>
</div>
</template>vue
<!-- src/views/User.vue -->
<script setup lang="ts">
import { useRoute, RouteLocationNormalizedLoaded } from 'vue-router'
import { ref, watch, Ref } from 'vue'
const route: RouteLocationNormalizedLoaded = useRoute()
// Access route params
const userId: Ref<string | string[]> = ref(route.params.id)
// Watch for param changes
watch(
() => route.params.id,
(newId: string | string[]) => {
userId.value = newId
// Fetch new user data
}
)
</script>
<template>
<div>
<h1>User {{ userId }}</h1>
<p>Full path: {{ route.fullPath }}</p>
<p>Query: {{ route.query }}</p>
</div>
</template>Programmatic Navigation
vue
<script setup>
import { useRouter, useRoute } from 'vue-router'
const router = useRouter()
const route = useRoute()
function goToUser(id) {
// Navigate by path
router.push(`/users/${id}`)
// Navigate by name with params
router.push({ name: 'user', params: { id } })
// Navigate with query
router.push({ path: '/search', query: { q: 'vue' } })
// Replace history (no back button)
router.replace('/new-page')
// Go back/forward
router.go(-1) // back
router.go(1) // forward
}
</script>
<template>
<div>
<button @click="goToUser(1)">Go to User 1</button>
<button @click="router.push('/')">Go Home</button>
<button @click="router.back()">Back</button>
</div>
</template>vue
<script setup lang="ts">
import { useRouter, useRoute, Router, RouteLocationNormalizedLoaded } from 'vue-router'
const router: Router = useRouter()
const route: RouteLocationNormalizedLoaded = useRoute()
function goToUser(id: number): void {
// Navigate by path
router.push(`/users/${id}`)
// Navigate by name with params
router.push({ name: 'user', params: { id: id.toString() } })
// Navigate with query
router.push({ path: '/search', query: { q: 'vue' } })
// Replace history (no back button)
router.replace('/new-page')
// Go back/forward
router.go(-1) // back
router.go(1) // forward
}
</script>
<template>
<div>
<button @click="goToUser(1)">Go to User 1</button>
<button @click="router.push('/')">Go Home</button>
<button @click="router.back()">Back</button>
</div>
</template>Navigation Guards
Global Guards
js
const router = createRouter({
history: createWebHistory(),
routes
})
// Global before guard
router.beforeEach((to, from) => {
const isAuthenticated = localStorage.getItem('token')
// Redirect to login if accessing protected route
if (to.meta.requiresAuth && !isAuthenticated) {
return { name: 'login', query: { redirect: to.fullPath } }
}
// Allow navigation
return true
})
// Global after hook
router.afterEach((to, from) => {
// Update document title
document.title = to.meta.title || 'My App'
})ts
import { createRouter, createWebHistory, RouteLocationNormalized } from 'vue-router'
const router = createRouter({
history: createWebHistory(),
routes
})
// Global before guard
router.beforeEach((to: RouteLocationNormalized, from: RouteLocationNormalized) => {
const isAuthenticated = localStorage.getItem('token')
// Redirect to login if accessing protected route
if (to.meta.requiresAuth && !isAuthenticated) {
return { name: 'login', query: { redirect: to.fullPath } }
}
// Allow navigation
return true
})
// Global after hook
router.afterEach((to: RouteLocationNormalized) => {
// Update document title
document.title = (to.meta.title as string) || 'My App'
})Route-Level Guards
js
const routes = [
{
path: '/dashboard',
name: 'dashboard',
component: () => import('@/views/Dashboard.vue'),
meta: { requiresAuth: true, title: 'Dashboard' },
beforeEnter: (to, from) => {
// Route-specific guard
const hasPermission = checkPermission()
if (!hasPermission) {
return { name: 'unauthorized' }
}
}
}
]ts
const routes: RouteRecordRaw[] = [
{
path: '/dashboard',
name: 'dashboard',
component: () => import('@/views/Dashboard.vue'),
meta: { requiresAuth: true, title: 'Dashboard' },
beforeEnter: (to, from) => {
// Route-specific guard
const hasPermission = checkPermission()
if (!hasPermission) {
return { name: 'unauthorized' }
}
}
}
]Component Guards
vue
<script setup>
import { onBeforeRouteLeave, onBeforeRouteUpdate } from 'vue-router'
import { ref } from 'vue'
const hasUnsavedChanges = ref(false)
// Before leaving this route
onBeforeRouteLeave((to, from) => {
if (hasUnsavedChanges.value) {
const answer = window.confirm('You have unsaved changes. Leave anyway?')
if (!answer) return false
}
})
// When route params change (same component)
onBeforeRouteUpdate((to, from) => {
// Fetch new data when params change
console.log('Route updated:', to.params)
})
</script>vue
<script setup lang="ts">
import { onBeforeRouteLeave, onBeforeRouteUpdate, RouteLocationNormalized } from 'vue-router'
import { ref, Ref } from 'vue'
const hasUnsavedChanges: Ref<boolean> = ref(false)
// Before leaving this route
onBeforeRouteLeave((to: RouteLocationNormalized, from: RouteLocationNormalized) => {
if (hasUnsavedChanges.value) {
const answer = window.confirm('You have unsaved changes. Leave anyway?')
if (!answer) return false
}
})
// When route params change (same component)
onBeforeRouteUpdate((to: RouteLocationNormalized, from: RouteLocationNormalized) => {
// Fetch new data when params change
console.log('Route updated:', to.params)
})
</script>Nested Routes
js
const routes = [
{
path: '/users',
component: () => import('@/views/UsersLayout.vue'),
children: [
{
path: '',
name: 'users',
component: () => import('@/views/UsersList.vue')
},
{
path: ':id',
name: 'user',
component: () => import('@/views/UserDetail.vue')
},
{
path: ':id/edit',
name: 'user-edit',
component: () => import('@/views/UserEdit.vue')
}
]
}
]ts
const routes: RouteRecordRaw[] = [
{
path: '/users',
component: () => import('@/views/UsersLayout.vue'),
children: [
{
path: '',
name: 'users',
component: () => import('@/views/UsersList.vue')
},
{
path: ':id',
name: 'user',
component: () => import('@/views/UserDetail.vue')
},
{
path: ':id/edit',
name: 'user-edit',
component: () => import('@/views/UserEdit.vue')
}
]
}
]Parent layout with nested RouterView:
vue
<!-- src/views/UsersLayout.vue -->
<template>
<div class="users-layout">
<nav>
<RouterLink :to="{ name: 'users' }">All Users</RouterLink>
</nav>
<!-- Nested routes render here -->
<RouterView />
</div>
</template>Lazy Loading Routes
js
const routes = [
{
path: '/',
name: 'home',
// Eagerly loaded
component: Home
},
{
path: '/about',
name: 'about',
// Lazy loaded - creates separate chunk
component: () => import('@/views/About.vue')
},
{
path: '/dashboard',
name: 'dashboard',
// With chunk name for better debugging
component: () => import(/* webpackChunkName: "dashboard" */ '@/views/Dashboard.vue')
}
]ts
const routes: RouteRecordRaw[] = [
{
path: '/',
name: 'home',
// Eagerly loaded
component: Home
},
{
path: '/about',
name: 'about',
// Lazy loaded - creates separate chunk
component: () => import('@/views/About.vue')
},
{
path: '/dashboard',
name: 'dashboard',
// With chunk name for better debugging
component: () => import(/* webpackChunkName: "dashboard" */ '@/views/Dashboard.vue')
}
]404 Not Found
js
const routes = [
// ... other routes
// Catch-all 404 route (must be last)
{
path: '/:pathMatch(.*)*',
name: 'not-found',
component: () => import('@/views/NotFound.vue')
}
]ts
const routes: RouteRecordRaw[] = [
// ... other routes
// Catch-all 404 route (must be last)
{
path: '/:pathMatch(.*)*',
name: 'not-found',
component: () => import('@/views/NotFound.vue')
}
]Summary
| Feature | Description |
|---|---|
RouterLink | Navigation component |
RouterView | Renders matched component |
useRouter() | Programmatic navigation |
useRoute() | Access current route |
| Route params | Dynamic path segments :id |
| Navigation guards | Control route access |
| Nested routes | Child route components |
| Lazy loading | Code splitting for routes |
What's Next?
Congratulations! You've completed the Vue.js tutorial series. Here are some next steps:
- Explore Pinia for state management
- Learn about Nuxt.js for server-side rendering
- Build a full application with everything you've learned!