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:
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:
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
Omit file extensions so VitePress can generate correct URLs based on your config:[ Getting Started ]( ./getting-started )
[ Getting Started ]( ../quickstart )
Don’t include .md or .html extensions: [ Getting Started ]( ./getting-started.md ) ❌
[ Getting Started ]( ./getting-started.html ) ❌
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
Check server support
These platforms support clean URLs automatically:
Enable in VitePress config
Add to .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:
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:
export default {
rewrites: {
'packages/:pkg/src/:slug*' : ':pkg/:slug*'
}
}
Function-Based Rewrites
Use a function for complex rewrite logic:
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:
export default {
paths () {
return [
{ params: { pkg: 'foo' }},
{ params: { pkg: 'bar' }}
]
}
}
This generates:
.
└─ packages
├─ foo.html
└─ bar.html
TypeScript Support
Use defineRoutes for type safety:
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:
File Structure
Paths Loader
Output
.
└─ 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 }}
})
}
}
export default {
async paths () {
const pkgs = await (
await fetch ( 'https://my-api.com/packages' )
). json ()
return pkgs . map (( pkg ) => {
return {
params: {
pkg: pkg . name ,
version: pkg . version
}
}
})
}
}
Watching Data Files
Automatically rebuild pages when data or templates change:
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:
# 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:
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