Skip to main content

Routing

VitePress uses file-based routing, which means your Markdown files automatically become pages based on their location in your project. No routing configuration needed!

File-Based Routing Basics

Every .md file in your source directory becomes an HTML page with the same path structure.

Simple Example

Given this file structure:
.
├─ guide
│  ├─ getting-started.md
│  └─ index.md
├─ index.md
└─ prologue.md
VitePress generates these URLs:
index.md                  →  /index.html (accessible as /)
prologue.md               →  /prologue.html
guide/index.md            →  /guide/index.html (accessible as /guide/)
guide/getting-started.md  →  /quickstart.html
The resulting HTML can be hosted on any web server that serves static files.

Project Root vs Source Directory

Understanding these concepts is key to organizing your VitePress project.

Project Root

The project root is where VitePress looks for the .vitepress directory:
.
├─ docs                    # ← project root
│  ├─ .vitepress           # config directory
│  ├─ getting-started.md
│  └─ index.md
└─ package.json
Run VitePress commands from your project directory:
vitepress dev docs
This produces:
docs/index.md            →  /index.html (accessible as /)
docs/getting-started.md  →  /getting-started.html

Source Directory

The source directory is where your Markdown files live. By default, it’s the same as the project root, but you can customize it:
.vitepress/config.js
export default {
  srcDir: 'src'
}
With this configuration:
.                          # project root
├─ .vitepress              # config dir
└─ src                     # ← source dir
   ├─ getting-started.md
   └─ index.md
Your routes become:
src/index.md            →  /index.html
src/getting-started.md  →  /getting-started.html
The srcDir option is useful for keeping your documentation separate from configuration files.

Linking Between Pages

Use both absolute and relative paths in your Markdown links.

Best Practices

Linking to Non-VitePress Pages

For pages not generated by VitePress, specify the target explicitly:
[Link to pure.html](/pure.html){target="_self"}
Or use HTML anchor tags:
<a href="/pure.html" target="_self">Link to pure.html</a>

Clean URLs

Remove .html extensions from your URLs for cleaner, more professional links.
Clean URLs require server-side support. Not all hosting platforms support this feature.

How It Works

By default: example.com/path.html
With clean URLs: example.com/path

Enable Clean URLs

1

Check server support

These platforms support clean URLs automatically:
2

Enable in VitePress config

Add to .vitepress/config.js:
.vitepress/config.js
export default {
  cleanUrls: true
}
With clean URLs enabled:
  • Internal links are generated without .html extensions
  • Client-side redirects from .html to clean URLs
  • Better SEO and user experience

Manual Clean URLs

If your server doesn’t support clean URLs, use this directory structure:
.
├─ getting-started
│  └─ index.md        →  /getting-started/
├─ installation
│  └─ index.md        →  /installation/
└─ index.md           →  /

Route Rewrites

Customize the mapping between source files and generated URLs - perfect for monorepos and complex structures.

Use Case: Monorepo Documentation

You have packages with documentation alongside source code:
.
└─ packages
   ├─ pkg-a
   │  └─ src
   │     ├─ foo.md
   │     └─ index.md
   └─ pkg-b
      └─ src
         ├─ bar.md
         └─ index.md
You want these URLs:
packages/pkg-a/src/index.md  →  /pkg-a/index.html
packages/pkg-a/src/foo.md    →  /pkg-a/foo.html
packages/pkg-b/src/index.md  →  /pkg-b/index.html
packages/pkg-b/src/bar.md    →  /pkg-b/bar.html

Static Rewrites

Define explicit mappings:
.vitepress/config.js
export default {
  rewrites: {
    'packages/pkg-a/src/index.md': 'pkg-a/index.md',
    'packages/pkg-a/src/foo.md': 'pkg-a/foo.md',
    'packages/pkg-b/src/index.md': 'pkg-b/index.md',
    'packages/pkg-b/src/bar.md': 'pkg-b/bar.md'
  }
}

Dynamic Rewrites with Parameters

Use route parameters to avoid repetition:
.vitepress/config.js
export default {
  rewrites: {
    'packages/:pkg/src/:slug*': ':pkg/:slug*'
  }
}
Rewrite patterns use the path-to-regexp package syntax.

Function-Based Rewrites

Use a function for complex rewrite logic:
.vitepress/config.js
export default {
  rewrites(id) {
    return id.replace(/^packages\/([^/]+)\/src\//, '$1/')
  }
}
Important: When using rewrites, relative links should be based on the rewritten paths, not the original file structure.

Dynamic Routes

Generate multiple pages from a single template using dynamic data - perfect for API documentation, blog posts, or product catalogs.

The Basics

Create a route template with parameters in square brackets:
.
└─ packages
   ├─ [pkg].md         # route template
   └─ [pkg].paths.js   # route paths loader
The [pkg] parameter will be replaced with actual values from your paths loader.

Paths Loader File

Create a .paths.js file that exports a paths function:
packages/[pkg].paths.js
export default {
  paths() {
    return [
      { params: { pkg: 'foo' }},
      { params: { pkg: 'bar' }}
    ]
  }
}
This generates:
.
└─ packages
   ├─ foo.html
   └─ bar.html

TypeScript Support

Use defineRoutes for type safety:
packages/[pkg].paths.ts
import { defineRoutes } from 'vitepress'

export default defineRoutes({
  async paths() {
    return [
      { params: { pkg: 'foo' } },
      { params: { pkg: 'bar' } }
    ]
  },
  
  async transformPageData(pageData) {
    pageData.title = `${pageData.title} · Packages`
  }
})
The defineRoutes helper provides TypeScript intellisense for route configuration.

Multiple Parameters

Use multiple parameters in your route:
.
└─ packages
   ├─ [pkg]-[version].md
   └─ [pkg]-[version].paths.js

Generating Paths from Data

Load data from local files or remote APIs:
import fs from 'fs'

export default {
  paths() {
    return fs
      .readdirSync('packages')
      .map((pkg) => {
        return { params: { pkg }}
      })
  }
}

Watching Data Files

Automatically rebuild pages when data or templates change:
posts/[slug].paths.js
import fs from 'node:fs'

export default {
  // Watch for changes during development
  watch: [
    './templates/**/*.njk',     // Template files
    '../data/**/*.json'         // Data files
  ],
  
  paths(watchedFiles) {
    // watchedFiles contains absolute paths of matched files
    const dataFiles = watchedFiles.filter(file => file.endsWith('.json'))
    
    return dataFiles.map(file => {
      const data = JSON.parse(fs.readFileSync(file, 'utf-8'))
      
      return {
        params: { slug: data.slug },
        content: data.content  // Inject content
      }
    })
  }
}
The watch option accepts glob patterns relative to the .paths.js file.

Accessing Parameters in Pages

Use parameters in your Markdown template:
packages/[pkg].md
# Package: {{ $params.pkg }}

Version: {{ $params.version }}
Or in Vue components:
<script setup>
import { useData } from 'vitepress'

const { params } = useData()

console.log(params.value.pkg)
</script>

<template>
  <h1>{{ params.pkg }}</h1>
</template>

Rendering Raw Content

Inject content from external sources without passing it as parameters:
export default {
  async paths() {
    const posts = await (
      await fetch('https://my-cms.com/blog-posts')
    ).json()
    
    return posts.map((post) => {
      return {
        params: { id: post.id },
        content: post.content  // Raw Markdown or HTML
      }
    })
  }
}
In your template, use the special @content marker:
---
title: Blog Post
---

<!-- @content -->
Avoid passing large content in params as it’s serialized in the client JavaScript bundle. Use the content property instead.

Setting a Base Path

If your site is deployed to a subdirectory, configure the base path:
.vitepress/config.js
export default {
  base: '/repo/'  // For https://username.github.io/repo/
}
GitHub/GitLab Pages: Set base to '/repo/' if deploying to user.github.io/repo/

Next Steps

Deploy Your Site

Learn how to build and deploy your VitePress site

Markdown Features

Explore VitePress’s powerful Markdown extensions

Asset Handling

Learn how to work with images and static files

Data Loading

Load data from local or remote sources

Build docs developers (and LLMs) love