- 前端
- 文档
【免费下载链接】vitepress
Vite & Vue powered static site generator.
导读
本文讲解如何将 VitePress 与各类 Headless CMS(无头 CMS)对接,把远程托管的文章、文档内容以构建期数据的方式拉取并渲染成静态页面。核心思路是围绕 VitePress 的**动态路由(Dynamic Routes)**机制展开:用.paths加载器在构建时从 CMS API 获取数据、生成每条路由的参数,再通过$params与<!-- @content -->语法把内容渲染进 Markdown 模板。读完本文,你将掌握一套与 CMS 无关的通用集成工作流,能够自行适配 Storyblok、Contentful、Sanity、自建 API 等任意内容源。
本文对应的官方文档为 docs/ja/guide/cms.md(英文版见 docs/en/guide/cms.md),所有底层原理均以当前仓库源码为准。
整体工作流
由于不同 CMS 的 API 形态、鉴权方式和返回结构各不相同,VitePress 没有提供针对特定 CMS 的官方插件,而是给出了一套通用流程,由开发者根据自身场景适配。整个集成围绕动态路由展开,因此在动手之前,请先确认你已经理解 动态路由的工作原理。
对接 CMS 的通用流程可以概括为三步:
- 若 CMS 需要认证,创建
.env存放 API Token,并通过loadEnv在路径加载器中读取; - 从 CMS 拉取所需数据,格式化为标准的路径数据(
params+ 可选content); - 在动态路由的 Markdown 页面中,用
$params渲染元信息、用<!-- @content -->渲染正文内容。
下面逐步展开。
前置知识:动态路由为何是集成的关键
VitePress 是静态站点生成器,所有页面路径必须在构建时确定下来。因此,一个包含方括号参数的文件(如posts/[id].md)必须配套一个同名的paths 加载器文件posts/[id].paths.js(也支持.ts、.mjs、.mts),加载器默认导出一个带paths方法的对象,返回一组{ params }结构,每个条目对应生成一个页面:
. └─ posts ├─ [id].md # 路由模板 └─ [id].paths.js # 路径加载器从源码看,src/node/plugins/dynamicRoutesPlugin.ts 中的resolveDynamicRoutes会按['js', 'ts', 'mjs', 'mts']的顺序查找与[id].md对应的.paths文件,找到后通过 Vite 的loadConfigFromFile加载并执行其中的paths()函数,再把返回结果与路由模板拼接,得到最终页面路径集合。如果找不到对应的 paths 文件,构建日志会输出警告并跳过该动态路由。
paths()返回的每个条目可以携带两类字段(见 src/node/plugins/dynamicRoutesPlugin.ts 中的UserRouteConfig):
params:路由参数,用于填充[id]占位符并生成页面路径,同时可在页面中通过$params读取;content:原始内容(Markdown 或 HTML),用于注入到页面正文,适合承载从 CMS 拉取的大段正文。
步骤一:用.env与loadEnv管理 CMS 凭据
如果你的 CMS API 需要认证(绝大多数托管 CMS 都要求携带 API Token),不要把 Token 硬编码进paths加载器。正确做法是将其放入项目根目录的.env文件,然后在加载器中通过 VitePress 导出的loadEnv读取:
// posts/[id].paths.js import { loadEnv } from 'vitepress' const env = loadEnv('', process.cwd())loadEnv的第一个参数是环境模式(''表示加载所有环境),第二个参数是 VitePress 项目根目录(process.cwd());loadEnv由 VitePress 从 Vite 重新导出,见 src/node/index.ts 中的export { loadEnv, type Plugin } from 'vite';- 读取后即可通过
env.VITE_XXX或env.CMS_API_TOKEN之类的键名访问对应变量,再在请求头中携带:
// posts/[id].paths.js import { loadEnv } from 'vitepress' const env = loadEnv('', process.cwd()) export default { async paths() { const data = await (await fetch('https://my-cms-api', { headers: { Authorization: `Bearer ${env.CMS_API_TOKEN}` } })).json() // ... } }注意:paths加载器运行在 Node.js 环境、仅在构建时执行,因此这里可以安全地使用服务端fetch(Node 18+ 内置)或任意 CMS 官方 Node 客户端库。
步骤二:从 CMS 拉取数据并格式化为路径数据
第二步是核心:调用 CMS API,把返回的原始数据映射成 VitePress 所需的路径数据结构。官方给出的通用模板如下:
// posts/[id].paths.js import { loadEnv } from 'vitepress' const env = loadEnv('', process.cwd()) export default { async paths() { // 需要的话,也可以使用各 CMS 的客户端库替代 fetch const data = await (await fetch('https://my-cms-api', { headers: { // 必要时在这里携带 Token } })).json() return data.map((entry) => { return { params: { id: entry.id /* title、author、date 等 */ }, content: entry.content } }) } }这段代码需要根据你的 CMS 做出三处适配:
- API 地址与鉴权:替换
https://my-cms-api,并视 CMS 要求补充Authorization、X-API-Key等请求头(从步骤一读取的env中取值); - 数据结构映射:
entry.id会填充到路由模板的[id]占位符(例如生成/posts/abc123.html),entry.content是待渲染的正文(原始 Markdown 或 HTML); - params 携带元信息:
title、author、date等字段一并放入params,页面内用$params直接渲染。
数据来源不止 API
官方在 routing 文档 中还展示了 paths 加载器的通用性:paths()在 Node.js 中构建期执行,因此数据源既可以是本地文件(fs.readdirSync),也可以是远程 API(fetch),甚至是文件系统与远程数据的组合。这意味着上述工作流同样适用于"内容仓库在本地、元数据在 CMS"的混合场景。
步骤三:在页面模板中渲染内容
路径数据准备好之后,剩下的就是在 Markdown 路由模板中消费它。官方示例:
# {{ $params.title }} - {{ $params.date }} 由 {{ $params.author }} 创建 <!-- @content -->这里有两个关键语法:
{{ $params.xxx }}:$params是 VitePress 提供的模板全局属性,可直接在 Vue 表达式中访问当前页面的动态路由参数。它由运行时 API 暴露,具体类型定义见 docs/en/reference/runtime-api.md;除了模板语法,你也可以在 Vue 组件中用useData()的paramsref 以编程方式读取(见 docs/ja/guide/routing.md)。<!-- @content -->:内容注入标记。当路径条目带有content字段时,VitePress 会把该字段的原始内容替换到这个注释的位置,并作为页面静态内容的一部分渲染,而不是作为运行时数据打包进客户端。源码中的替换逻辑位于 src/node/plugins/dynamicRoutesPlugin.ts:先读取[id].md模板原文,再用正则<!--\s*@content\s*-->定位注入点,将content(并对其中的$做$$$转义以兼容模板字符串)替换进去。
为什么正文要走content而不是params
这一点非常重要:params最终会被序列化进客户端的 JS payload 中(见 src/node/markdownToVue.ts 中参数注入标记的解析,以及 src/node/markdownToVue.ts 中params被写入页面数据的逻辑)。因此:
- 适合放
params:id、title、author、date等轻量元数据; - 不适合放
params:从远程 CMS 拉取的大段 Markdown/HTML 正文——它们会撑大 JS bundle,拖慢首屏; - 正文应通过
content字段传递,让 VitePress 在构建期直接渲染为静态 HTML,避免把大段原始内容塞进客户端数据。
底层原理:动态路由插件如何工作
结合源码可以更透彻地理解这套工作流。核心实现在 src/node/plugins/dynamicRoutesPlugin.ts:
- 路径解析(L226-L360):
resolveDynamicRoutes扫描srcDir下所有含[参数]的 Markdown 文件,找到对应的.paths加载器并执行,用正则dynamicRouteRE = /\[(\w+?)\]/g把每个条目params中的值替换回路由模板,得到形如posts/foo.md的真实文件路径; - 内容注入(L163-L181):
load钩子中对匹配的动态路由,把content注入模板、把params用__VP_PARAMS_START/__VP_PARAMS_END__特殊标记包裹后随文件内容一起返回,由 src/node/markdownToVue.ts 在编译时解析回params并写入页面数据; - 开发期热更新(L183-L215):
hotUpdate钩子监听 paths 加载器及其依赖、以及watch模式匹配文件的变化,触发resolvePages重新解析路由——这就是开发时"改了 CMS 数据或模板文件,页面自动重建"的机制。
进阶:用defineRoutes获得类型安全与更多钩子
如果使用 TypeScript 编写 paths 加载器,官方推荐用vitepress导出的defineRoutes包裹默认导出,以获得paths、watch、transformPageData等钩子的类型提示。defineRoutes在 src/node/plugins/dynamicRoutesPlugin.ts 中定义,本质上只是类型推断辅助函数。一个贴合 CMS 场景的完整示例:
// posts/[id].paths.ts import { defineRoutes } from 'vitepress' import { loadEnv } from 'vitepress' const env = loadEnv('', process.cwd()) export default defineRoutes({ // 监听本地模板/数据文件,开发期变化时自动重建对应页面 watch: ['./templates/**/*.njk', '../data/**/*.json'], async paths() { const posts = await (await fetch('https://my-cms-api/posts', { headers: { Authorization: `Bearer ${env.CMS_API_TOKEN}` } })).json() return posts.map((post) => ({ params: { id: post.id, title: post.title, author: post.author, date: post.date }, content: post.content // 原始 Markdown 正文 })) }, // 可选:在页面数据生成后做二次加工 async transformPageData(pageData) { pageData.title = `${pageData.title} · Blog` } })仓库自带的一个可运行示例是tests/e2e/dynamic-routes/[id].paths.ts,它演示了defineRoutes与watch、transformPageData的组合用法;对应的端到端测试tests/e2e/dynamic-routes/dynamic-routes.test.ts 验证了"访问/dynamic-routes/foo能渲染出对应params"这一行为,可作为你实现 CMS 集成后自测的参考模板。
watch选项与数据加载器中的语义一致:接受 glob 模式、相对.paths文件解析、开发期变化触发页面重建与 HMR;生产构建时所有页面一次性生成,与watch无关。
实战注意事项
- 构建时机:
paths()只在构建期执行,CMS 内容更新后需要重新vitepress build才能反映到站点上;持续集成(CI)中可配置定时或 webhook 触发的重建任务; - Token 安全:
.env应加入.gitignore,不要在params或content中携带敏感信息(它们会被写入生成的静态产物); - 正文体积:坚持用
content承载正文、用轻量params承载元数据,避免客户端数据膨胀; - 错误处理:建议在
paths()中为 CMS 请求失败添加兜底逻辑(如返回空数组或抛出带上下文的错误),避免构建在 API 抖动时中断; - 动态路由依赖项:每个
[param].md都必须有对应的.paths文件,否则构建日志会告警并跳过该路由,这一点同样适用于 CMS 集成场景。
参考资源
- 本文核心文档:docs/ja/guide/cms.md
- 动态路由完整说明:docs/ja/guide/routing.md
- 动态路由插件源码:src/node/plugins/dynamicRoutesPlugin.ts
- 参数解析与页面数据生成:src/node/markdownToVue.ts
loadEnv导出:src/node/index.ts- 运行时
$params/useData说明:docs/en/reference/runtime-api.md - 端到端测试与示例:tests/e2e/dynamic-routes/dynamic-routes.test.ts
- 前端
- 文档
【免费下载链接】vitepress
Vite & Vue powered static site generator.
相关推荐
VitePress 接入 Headless CMS 实战:基于动态路由与路径加载器构建内容驱动站点
VitePress 接入 Headless CMS 实战:基于动态路由与路径加载器构建内容驱动站点 VitePress 作为基于 Vite 与 Vue 的静态站
前端文档VitePress 接入 Headless CMS 实战:动态路由、paths 加载器与 `@content` 内容注入全指南
VitePress 接入 Headless CMS 实战:动态路由、paths 加载器与 @content 内容注入全指南 本篇指南聚焦于一个典型应用场景:如何
前端文档Qwen3-4B性能实测:27.86 tokens/s!MindSpore+NPU部署终极优化方案
Qwen3 4B性能实测:27.86 tokens/s!MindSpore+NPU部署终极优化方案 Qwen3 4B是Qwen大模型系列的新一代版本,在自然语言
前端文档
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考