Routing
Learn how routing works in VitePress and how to configure custom routes.
File-based Routing
Basic Routing
VitePress uses file-based routing where the file structure determines the URL structure:
docs/
├── index.md → /
├── about.md → /about
├── guide/
│ ├── index.md → /guide/
│ ├── getting-started.md → /guide/getting-started
│ └── advanced.md → /guide/advanced
└── api/
├── index.md → /api/
└── reference.md → /api/reference
Clean URLs
Enable clean URLs to remove .html
extensions:
// .vitepress/config.js
export default {
cleanUrls: true
}
With clean URLs:
/guide/getting-started.html
becomes/guide/getting-started
/api/reference.html
becomes/api/reference
Dynamic Routes
Route Parameters
Create dynamic routes using square brackets:
docs/
├── users/
│ ├── [id].md → /users/:id
│ └── [id]/
│ └── profile.md → /users/:id/profile
└── blog/
└── [slug].md → /blog/:slug
Accessing Route Parameters
Access parameters in your components:
<script setup>
import { useData, useRoute } from 'vitepress'
const route = useRoute()
const { params } = route
// Access route parameters
const userId = params.id
const blogSlug = params.slug
</script>
<template>
<div>
<h1>User Profile</h1>
<p>User ID: {{ userId }}</p>
</div>
</template>
Generating Dynamic Routes
Generate routes programmatically:
// .vitepress/config.js
export default {
async buildEnd() {
// Fetch data for dynamic routes
const users = await fetchUsers()
// Generate user profile pages
for (const user of users) {
const content = `---
title: ${user.name}
---
# ${user.name}
Email: ${user.email}
Bio: ${user.bio}
`
await writeFile(`docs/users/${user.id}.md`, content)
}
}
}
Route Configuration
Custom Rewrites
Configure custom URL rewrites:
// .vitepress/config.js
export default {
rewrites: {
// Rewrite patterns
'packages/:pkg/(.*)': ':pkg/index.md',
'guide/(.*)': 'docs/$1.md',
// Simple redirects
'old-page': 'new-page',
'legacy/(.*)': 'modern/$1'
}
}
Route Aliases
Create route aliases:
// .vitepress/config.js
export default {
rewrites: {
// Multiple URLs for the same content
'docs/getting-started': 'guide/index.md',
'start': 'guide/index.md',
'begin': 'guide/index.md'
}
}
Navigation
Programmatic Navigation
Navigate programmatically using the router:
<script setup>
import { useRouter } from 'vitepress'
const router = useRouter()
function navigateToGuide() {
router.go('/guide/getting-started')
}
function goBack() {
router.go(-1) // Go back in history
}
function goForward() {
router.go(1) // Go forward in history
}
</script>
<template>
<div>
<button @click="navigateToGuide">Go to Guide</button>
<button @click="goBack">Back</button>
<button @click="goForward">Forward</button>
</div>
</template>
Route Guards
Implement route guards for navigation control:
// .vitepress/theme/index.js
import DefaultTheme from 'vitepress/theme'
export default {
extends: DefaultTheme,
enhanceApp({ app, router }) {
// Before route change
router.onBeforeRouteChange = (to) => {
console.log('Navigating to:', to)
// Check authentication
if (to.startsWith('/admin') && !isAuthenticated()) {
router.go('/login')
return false // Prevent navigation
}
return true // Allow navigation
}
// After route change
router.onAfterRouteChanged = (to) => {
console.log('Navigated to:', to)
// Track page views
gtag('config', 'GA_MEASUREMENT_ID', {
page_path: to
})
}
}
}
Route Data
Page Data
Access page-specific data:
<script setup>
import { useData } from 'vitepress'
const { page, frontmatter } = useData()
// Page information
console.log('Current page:', page.value.relativePath)
console.log('Page title:', page.value.title)
console.log('Frontmatter:', frontmatter.value)
</script>
<template>
<div>
<h1>{{ frontmatter.title || page.title }}</h1>
<p>Path: {{ page.relativePath }}</p>
<p>Last updated: {{ page.lastUpdated }}</p>
</div>
</template>
Site Data
Access site-wide data:
<script setup>
import { useData } from 'vitepress'
const { site, theme } = useData()
// Site information
console.log('Site title:', site.value.title)
console.log('Site description:', site.value.description)
console.log('Theme config:', theme.value)
</script>
<template>
<div>
<h1>{{ site.title }}</h1>
<p>{{ site.description }}</p>
</div>
</template>
Route Metadata
Frontmatter Configuration
Configure route behavior with frontmatter:
---
title: Custom Page Title
layout: custom
permalink: /custom-url
redirect: /new-location
---
# Page Content
Custom Route Data
Add custom data to routes:
---
title: API Reference
category: documentation
tags: [api, reference, docs]
author: John Doe
lastReviewed: 2024-01-15
---
# API Reference
This page contains API documentation.
Access custom data in components:
<script setup>
import { useData } from 'vitepress'
const { frontmatter } = useData()
const category = frontmatter.value.category
const tags = frontmatter.value.tags || []
const author = frontmatter.value.author
</script>
<template>
<div>
<div class="metadata">
<span class="category">{{ category }}</span>
<span class="tags">
<span v-for="tag in tags" :key="tag" class="tag">
{{ tag }}
</span>
</span>
<span class="author">By {{ author }}</span>
</div>
</div>
</template>
Route Hooks
Lifecycle Hooks
Use route lifecycle hooks:
<script setup>
import { onMounted, onUnmounted, watch } from 'vue'
import { useRoute } from 'vitepress'
const route = useRoute()
// Watch for route changes
watch(() => route.path, (newPath, oldPath) => {
console.log(`Route changed from ${oldPath} to ${newPath}`)
// Update page title
document.title = `${route.data.title} | My Site`
// Track analytics
trackPageView(newPath)
})
onMounted(() => {
console.log('Page mounted:', route.path)
})
onUnmounted(() => {
console.log('Page unmounted:', route.path)
})
</script>
Custom Route Handlers
Create custom route handlers:
// utils/router.js
export function createRouteHandler() {
const handlers = new Map()
function addHandler(pattern, handler) {
handlers.set(pattern, handler)
}
function handleRoute(path) {
for (const [pattern, handler] of handlers) {
const match = path.match(new RegExp(pattern))
if (match) {
return handler(match)
}
}
}
return { addHandler, handleRoute }
}
// Usage
const routeHandler = createRouteHandler()
routeHandler.addHandler('/api/(.*)', (match) => {
console.log('API route accessed:', match[1])
})
routeHandler.addHandler('/blog/([^/]+)', (match) => {
console.log('Blog post accessed:', match[1])
})
Advanced Routing
Nested Routes
Handle nested route structures:
docs/
├── admin/
│ ├── index.md → /admin/
│ ├── users/
│ │ ├── index.md → /admin/users/
│ │ ├── [id].md → /admin/users/:id
│ │ └── create.md → /admin/users/create
│ └── settings/
│ ├── index.md → /admin/settings/
│ └── profile.md → /admin/settings/profile
Route Middleware
Implement route middleware:
// middleware/auth.js
export function authMiddleware(to, from, next) {
if (to.path.startsWith('/admin')) {
if (!isAuthenticated()) {
next('/login')
return
}
}
next()
}
// middleware/analytics.js
export function analyticsMiddleware(to, from, next) {
// Track page views
if (typeof gtag !== 'undefined') {
gtag('config', 'GA_MEASUREMENT_ID', {
page_path: to.path
})
}
next()
}
Route Caching
Implement route-level caching:
// utils/route-cache.js
const routeCache = new Map()
export function getCachedRoute(path) {
return routeCache.get(path)
}
export function setCachedRoute(path, data) {
routeCache.set(path, {
data,
timestamp: Date.now()
})
}
export function clearExpiredCache(maxAge = 5 * 60 * 1000) {
const now = Date.now()
for (const [path, cached] of routeCache) {
if (now - cached.timestamp > maxAge) {
routeCache.delete(path)
}
}
}
SEO and Routes
Route-based SEO
Optimize SEO for different routes:
---
title: Complete Guide to VitePress Routing
description: Learn everything about routing in VitePress including dynamic routes, navigation, and SEO optimization
head:
- - meta
- name: keywords
content: vitepress, routing, navigation, vue, documentation
- - meta
- property: og:title
content: VitePress Routing Guide
- - meta
- property: og:url
content: https://example.com/guide/routing
- - link
- rel: canonical
href: https://example.com/guide/routing
---
Sitemap Generation
Generate sitemaps based on routes:
// .vitepress/config.js
export default {
sitemap: {
hostname: 'https://example.com',
transformItems: (items) => {
return items.map(item => {
// Set priority based on route depth
const depth = item.url.split('/').length - 1
const priority = Math.max(0.1, 1.0 - (depth * 0.2))
return {
...item,
priority,
changefreq: getChangeFreq(item.url)
}
})
}
}
}
function getChangeFreq(url) {
if (url === '/') return 'daily'
if (url.startsWith('/blog/')) return 'weekly'
if (url.startsWith('/api/')) return 'monthly'
return 'monthly'
}
Testing Routes
Route Testing
Test route functionality:
// tests/routes.test.js
import { describe, it, expect } from 'vitest'
import { createRouter } from '../utils/router'
describe('Route Handling', () => {
const router = createRouter()
it('should handle basic routes', () => {
expect(router.resolve('/guide')).toBe('/guide/index.html')
expect(router.resolve('/api/reference')).toBe('/api/reference.html')
})
it('should handle dynamic routes', () => {
expect(router.resolve('/users/123')).toBe('/users/[id].html')
expect(router.getParams('/users/123')).toEqual({ id: '123' })
})
it('should handle rewrites', () => {
expect(router.resolve('/docs/getting-started')).toBe('/guide/index.html')
})
})
Navigation Testing
Test navigation behavior:
// tests/navigation.test.js
import { mount } from '@vue/test-utils'
import { createRouter } from 'vue-router'
import NavigationComponent from '../components/Navigation.vue'
describe('Navigation', () => {
it('should navigate to correct routes', async () => {
const router = createRouter({
history: createMemoryHistory(),
routes: [
{ path: '/', component: { template: '<div>Home</div>' } },
{ path: '/guide', component: { template: '<div>Guide</div>' } }
]
})
const wrapper = mount(NavigationComponent, {
global: {
plugins: [router]
}
})
await wrapper.find('[data-test="guide-link"]').trigger('click')
expect(router.currentRoute.value.path).toBe('/guide')
})
})
Best Practices
Route Organization
- Use consistent naming conventions
- Group related routes in directories
- Keep route hierarchy shallow when possible
- Use descriptive route names
Performance
- Implement route-level code splitting
- Use lazy loading for heavy components
- Cache route data when appropriate
- Minimize route middleware overhead
SEO
- Use clean, descriptive URLs
- Implement proper canonical URLs
- Generate comprehensive sitemaps
- Use appropriate meta tags per route
User Experience
- Provide loading states for route transitions
- Handle route errors gracefully
- Implement proper navigation feedback
- Ensure accessibility in navigation