Skip to content

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:

javascript
// .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:

vue
<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:

javascript
// .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:

javascript
// .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:

javascript
// .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'
  }
}

Programmatic Navigation

Navigate programmatically using the router:

vue
<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:

javascript
// .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:

vue
<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:

vue
<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:

markdown
---
title: Custom Page Title
layout: custom
permalink: /custom-url
redirect: /new-location
---

# Page Content

Custom Route Data

Add custom data to routes:

markdown
---
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:

vue
<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:

vue
<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:

javascript
// 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:

javascript
// 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:

javascript
// 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:

markdown
---
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:

javascript
// .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:

javascript
// 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')
  })
})

Test navigation behavior:

javascript
// 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

VitePress Development Guide