路由
基于文件的路由
VitePress 使用基于文件的路由,这意味着生成的 HTML 页面是从源 Markdown 文件的目录结构映射而来的。例如,给定以下目录结构:
docs/
├─ guide/
│ ├─ getting-started.md
│ └─ index.md
├─ index.md
└─ prologue.md
生成的 HTML 页面将是:
index.md --> /index.html (可以通过 / 访问)
prologue.md --> /prologue.html
guide/index.md --> /guide/index.html (可以通过 /guide/ 访问)
guide/getting-started.md --> /guide/getting-started.html
生成的 HTML 可以托管在任何能够提供静态文件的 Web 服务器上。
根目录和源目录
VitePress 项目中有两个重要的概念:项目根目录和源目录。
项目根目录
项目根目录是 VitePress 将尝试寻找 .vitepress
特殊目录的地方。.vitepress
目录是 VitePress 配置文件、开发服务器缓存、构建输出和可选主题自定义代码的预留位置。
当你从命令行运行 vitepress dev
或 vitepress build
时,VitePress 将使用当前工作目录作为项目根目录。要将子目录指定为根目录,你需要将相对路径传递给命令:
vitepress dev docs
这将使用 ./docs
作为项目根目录,并寻找 ./docs/.vitepress
配置目录。
源目录
源目录是 Markdown 源文件所在的位置。默认情况下,它与项目根目录相同。但是,你可以通过 srcDir
配置选项对其进行配置。
srcDir
选项是相对于项目根目录解析的。例如,使用 srcDir: 'src'
,你的文件结构将如下所示:
. # 项目根目录
├─ .vitepress # 配置目录
└─ src # 源目录
├─ getting-started.md
└─ index.md
生成的源文件到 HTML 的映射:
src/index.md --> /index.html
src/getting-started.md --> /getting-started.html
页面间链接
你可以在页面之间使用相对路径进行链接。请注意,虽然你可以省略 .md
扩展名,但如果你引用的页面在不同的目录中,则必须包含目录路径:
<!-- 做 -->
[入门指南](./getting-started)
[入门指南](../guide/getting-started)
<!-- 不要做 -->
[入门指南](../getting-started)
[入门指南](getting-started.html)
另请参阅资源处理以了解如何链接到静态资源。
链接到非 VitePress 页面
如果你想链接到站点内不是由 VitePress 生成的页面,则需要使用完整的 URL(在新窗口中打开)或明确指定目标:
[Link to pure.html](../../pure.html){target="_self"}
生成简洁的 URL
服务器支持必需
要提供简洁的 URL,需要服务器端支持。
默认情况下,VitePress 将传入的 URL 解析为以 .html
结尾。但是,一些用户可能更喜欢没有 .html
扩展名的"简洁 URL"——例如,example.com/path
而不是 example.com/path.html
。
一些服务器或托管平台(例如 Netlify、Vercel、GitHub Pages)提供了将 /foo
这样的 URL 映射到 /foo.html
的能力(如果存在),而无需重定向:
- Netlify 和 GitHub Pages 默认支持此功能。
- Vercel 需要在
vercel.json
中启用cleanUrls
选项。
如果此功能对你可用,你还可以启用 VitePress 自己的 cleanUrls
配置选项,以便:
- 页面之间的内部链接生成时不带
.html
扩展名 - 如果当前路径以
.html
结尾,路由器将执行客户端重定向到无扩展名路径
但是,如果你无法为服务器配置此类支持,则必须手动使用以下目录结构:
.
├─ getting-started
│ └─ index.md
├─ installation
│ └─ index.md
└─ index.md
路由重写
你可以自定义源目录结构和生成页面之间的映射。当你有复杂的文档结构时,这很有用。例如,假设你有一个包含多个包的 monorepo,并且希望将文档与源文件一起放置:
.
├─ packages
│ ├─ pkg-a
│ │ └─ src
│ │ ├─ pkg-a-code.ts
│ │ └─ pkg-a-docs.md
│ └─ pkg-b
│ └─ src
│ ├─ pkg-b-code.ts
│ └─ pkg-b-docs.md
你希望 VitePress 页面生成如下:
packages/pkg-a/src/pkg-a-docs.md --> /pkg-a/index.html
packages/pkg-b/src/pkg-b-docs.md --> /pkg-b/index.html
你可以通过配置 rewrites
选项来实现此目的:
// .vitepress/config.ts
export default {
rewrites: {
'packages/pkg-a/src/pkg-a-docs.md': 'pkg-a/index.md',
'packages/pkg-b/src/pkg-b-docs.md': 'pkg-b/index.md'
}
}
rewrites
选项还支持动态路由参数。在上面的示例中,如果你有很多包,列出每个包会很冗长。给定上面的文件结构,你可以简化配置:
export default {
rewrites: {
'packages/:pkg/src/(.*)': ':pkg/index.md'
}
}
重写路径使用 path-to-regexp
包编译——有关更高级的语法,请参阅其文档。
重写时的相对链接
启用重写时,相对链接应基于重写的路径。例如,要从 packages/pkg-a/src/pkg-a-docs.md
创建到 packages/pkg-b/src/pkg-b-docs.md
的相对链接,你应该使用:
[Link to PKG B](../pkg-b/index)
动态路由
你可以使用单个 Markdown 文件和动态数据生成许多页面。例如,你可以创建一个 packages/[pkg].md
文件,为项目中的每个包生成相应的页面。这里,[pkg]
段是一个路由参数,用于区分每个页面。
路径加载文件
由于 VitePress 是静态站点生成器,可能的页面路径必须在构建时确定。因此,动态路由页面必须附带一个路径加载文件。对于 packages/[pkg].md
,我们需要 packages/[pkg].paths.js
(也支持 .ts
):
// packages/[pkg].paths.js
export default {
paths() {
return [
{ params: { pkg: 'pkg-a' }},
{ params: { pkg: 'pkg-b' }}
]
}
}
paths
函数应该返回一个具有 params
属性的对象数组。每个对象都将生成相应的页面。
所以如果我们有以下路径:
export default {
paths() {
return [
{ params: { pkg: 'pkg-a' }},
{ params: { pkg: 'pkg-b' }}
]
}
}
那么生成的 HTML 页面将是:
/packages/pkg-a.html
/packages/pkg-b.html
多个参数
动态路由可以包含多个参数:
文件结构
─ packages
└─ [pkg]
└─ [version]
└─ index.md
路径文件
export default {
paths: () => [
{ params: { pkg: 'pkg-a', version: '1.0.0' }},
{ params: { pkg: 'pkg-a', version: '2.0.0' }},
{ params: { pkg: 'pkg-b', version: '1.0.0' }},
{ params: { pkg: 'pkg-b', version: '2.0.0' }}
]
}
生成的页面
/packages/pkg-a/1.0.0/index.html
/packages/pkg-a/2.0.0/index.html
/packages/pkg-b/1.0.0/index.html
/packages/pkg-b/2.0.0/index.html
动态生成路径
路径加载模块在 Node.js 中运行,只在构建时执行。你可以使用任何数据(本地或远程)动态生成路径数组。
从本地文件生成路径:
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
}
}
})
}
}
在页面中访问参数
你可以使用参数将额外数据传递给每个页面。Markdown 路由文件可以通过 $params
全局属性在 Vue 表达式中访问当前页面参数:
- package name: {{ $params.pkg }}
- version: {{ $params.version }}
你还可以通过 useData
运行时 API 访问当前页面的参数。这在 Markdown 文件和 Vue 组件中都有效:
<script setup>
import { useData } from 'vitepress'
const { params } = useData()
console.log(params.pkg)
</script>
渲染原始内容
传递给页面的参数将在客户端 JavaScript 有效负载中序列化,因此你应该避免在参数中传递重数据,例如在远程 CMS 中获取的原始 Markdown 或 HTML 内容。
相反,你可以使用每个路径对象上的 content
属性将此类内容传递给每个页面:
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, /* 其他 url 参数 */ },
content: post.content // 原始 Markdown 或 HTML
}
})
}
}
然后,使用以下特殊语法将内容渲染为页面本身的一部分:
# {{ $params.id }}
<Content />
<Content />
是一个内置组件,它将渲染路径对象的 content
属性中包含的原始内容。