VitePress 与 Docusaurus 站点写作工具链:从最小站点到迁移治理
你可能遇到过一种很像“文档已经完成”的假象:本机执行开发命令,浏览器能看到首页,Markdown 也能热更新;一旦交给 CI 构建,再放到 https://example.com/handbook/ 这样的子路径,导航、图片和搜索就开始报错。另一个团队则相反,站点可以发布,但某个版本的 API 文档、某个语言目录和某个插件生成的页面互相覆盖,最后没人说得清哪一个文件才是事实来源。
这两个问题都不是 Markdown 语法问题,而是站点生成器对“内容如何成为页面”的建模不同。VitePress 把目录和 .md 文件直接映射成路由,再通过 Vite、Vue 和主题扩展渲染;Docusaurus 把文档插件、侧边栏、版本和插件实例组成一组内容管线,再由 React 主题把结果渲染成页面。前者更接近“文件系统就是知识树”,后者更接近“插件实例拥有一套内容模型”。理解这条差异,后面的 base、routeBasePath、MDX、翻译和迁移才不会变成孤立配置。
这两个工具的安装入口分别在 VitePress 1.x Getting Started 与 Docusaurus Installation。VitePress 官网主文档已经面向 2.0.0-alpha 预发布线,示例命令是 vitepress@next;npm 默认标签仍指向稳定的 1.6.x。因此,阅读 VitePress 文档时先看页面左上角的版本,不要拿 2.x 预发布配置指导 1.x 生产站点。Docusaurus 新站则以 3.x 为基线,所有 @docusaurus/* 包必须保持同一版本。
先把两种站点想成两条流水线
VitePress 的输入通常是一个站点根目录:根目录下面有 Markdown 文件,以及保留的 .vitepress 配置目录。guide/getting-started.md 通常对应 /guide/getting-started.html,guide/index.md 对应 /guide/。页面本身是 Markdown,主题配置放在 themeConfig,需要交互时才把 Vue 组件引入 Markdown 或自定义主题。它的强项是内容路径短、静态输出直接、文档站启动成本低,适合源码仓库旁边的工程手册、API 参考和单一主版本知识库。
Docusaurus 的默认站点则同时有 docs/、blog/、src/pages/、static/、docusaurus.config.js 和 sidebars.js。一个文档页先由 @docusaurus/plugin-content-docs 读取,再进入侧边栏和版本数据,最后由 preset/theme 生成路由。官方文档把 docs 能力拆成“单个页面、侧边栏、版本、插件实例”四层;这意味着同一个站点可以拥有不同目录、不同侧边栏和不同版本的多套文档。它更适合产品文档与博客并存、需要维护多个发布版本、需要 React 组件和插件生态的团队。
两者都属于静态站点生成器:构建阶段读取文件和配置,生成可以交给静态文件服务器、CDN 或对象存储的产物。它们不会自动替你提供数据库、登录、草稿权限或实时内容后台。需要登录后可见、按租户动态查询、运行时检索内部数据时,要把动态服务单独设计;把密级控制寄托在“页面没有链接”上,最终只会把风险推迟到搜索索引、旧构建目录或 CDN 缓存。
版本基线也要先说清。VitePress 稳定 1.6.x 文档要求 Node 18 或更高;面向 2.0.0-alpha 的主线入门页要求 Node 22 或更高,并使用 vitepress@next。Docusaurus 3.x 的安装页要求 Node 20 或更高。Node.js 官方发布线说明建议生产应用使用仍受支持的 LTS 发布线。团队不应把“能安装”当成兼容承诺:把 Node 主版本、包管理器、框架主版本和锁文件一起写入工程基线,预发布版本只放在隔离分支验证,不能与稳定线共用一次无回退点的升级。
用两个干净目录完成安装初始化
先做最小目录实验,避免直接把框架脚手架塞进已有站点。VitePress 官方建议独立站点使用 docs 作为源目录,并把 .vitepress 当作配置、缓存和构建输出的保留目录。稳定线 Getting Started还特别提醒 VitePress 是 ESM-only 包,配置文件应使用 ESM,或用 .mjs、.mts 扩展名。下面先用稳定 1.x 跑通生产基线:
mkdir site-vp
cd site-vp
npm init -y
npm add -D vitepress@1
npm pkg set type=module
npm pkg set scripts.docs:dev="vitepress dev docs"
npm pkg set scripts.docs:build="vitepress build docs"
npm pkg set scripts.docs:preview="vitepress preview docs"
npx vitepress init只有明确评估 2.x 预发布能力时,才把安装项改成 vitepress@next 并把 Node 基线提高到 22;验证完成后应保留独立锁文件和产物对比,不能让 @next 在日常安装中静默替换稳定线。
向导需要交互式终端;如果 CI 或受限终端没有可用 TTY,就不要把“向导成功”当成构建证据,可以按同一文件模型手工创建 docs/index.md 和 docs/.vitepress/config.mts。最小配置可以先只保留标题和本地搜索:
// docs/.vitepress/config.mts
import { defineConfig } from 'vitepress'
export default defineConfig({
title: '工程知识站',
description: '可复现的研发文档',
base: process.env.DOCS_BASE ?? '/',
themeConfig: {
search: { provider: 'local' },
nav: [{ text: '指南', link: '/guide/' }],
sidebar: [{ text: '指南', items: [{ text: '入口', link: '/guide/' }] }]
}
})base 先用环境变量接入,是因为本地根路径和 GitHub Pages、Nginx 子路径很可能不同;不要在 Markdown 每个链接里手工拼接部署前缀。VitePress Site Config规定 base 必须以 / 开头和结尾,Markdown 中的站内链接会自动处理这个前缀。配置写好后,创建 docs/guide/index.md,然后执行:
npm run docs:dev -- --host 127.0.0.1 --port 4174
# 另一个终端验证
Invoke-WebRequest http://127.0.0.1:4174/guide/ | Select-Object StatusCode
npm run docs:build
npm run docs:preview -- --host 127.0.0.1 --port 4176Docusaurus 通过官方脚手架初始化。classic 模板带有文档、博客、页面、经典主题和常用 CSS 入口;它不是必须的生产架构,而是帮助你快速获得一套可运行内容模型的起点。Docusaurus 安装文档给出的等价入口包括 npx create-docusaurus@latest、npm init docusaurus、pnpm create docusaurus 和其他包管理器命令。最小初始化可以这样做:
npx create-docusaurus@latest site-docs classic --javascript
cd site-docs
npm install
npm start -- --host 127.0.0.1 --port 4175
# 另一个终端验证
Invoke-WebRequest http://127.0.0.1:4175/ | Select-Object StatusCode
npm run build
npm run serve -- --host 127.0.0.1 --port 4177
# 另一个终端验证生产产物
Invoke-WebRequest http://127.0.0.1:4177/ | Select-Object StatusCodeDocusaurus 的构建结果默认写入 build/,而不是把源码目录本身当作可部署目录。发布前应运行 npm run build,再用 npm run serve 服务这个静态结果;开发服务器的热更新状态不能代替生产产物检查。升级时不要只改一个包,官方安装文档要求所有 @docusaurus/* 包保持同一版本,并在安装后用 npx docusaurus --version 检查解析结果。
配置字段会改变页面和产物
VitePress 配置大致分成三层。站点层的 title、description、lang、base、srcDir、outDir 和 cleanUrls 改变 HTML 元数据、源目录、输出目录和 URL 形态;主题层的 themeConfig.nav、sidebar、search、outline、editLink 和 lastUpdated 改变导航与页面 UI;markdown、vite、vue 则把 Markdown-it、Vite 和 Vue 的能力带进渲染管线。官方配置参考说明了配置文件可以是 .js、.ts、.mjs 或 .mts,目录级配置还会继承并合并上层设置。
srcDir 是一个容易被低估的字段。把源文件移到 docs/content 后,路由看起来仍然是 /guide/intro,但相对图片、代码片段和脚本的解析基准已经改变;如果迁移时只移动 Markdown,没有同步静态资源与 srcDir,开发服务器可能仍能显示缓存页面,干净构建却找不到文件。outDir 也应当只指向可清理的产物目录,不要让构建覆盖源码或仓库中需要审查的静态资产。
Docusaurus 则把配置集中在 docusaurus.config.js 或 docusaurus.config.ts。title、url、baseUrl 和 favicon 影响页面元数据及资源地址;organizationName、projectName 等字段服务于 GitHub Pages 部署;presets 承载 docs、blog 和 theme 的组合;plugins 和 themes 承载额外内容源、搜索、分析或自定义主题。官方配置文档明确说明配置文件在 Node.js 中运行,并且未知字段会被拒绝;自定义业务字段要放进 customFields,不要随意把配置对象当成无约束 JSON。
下面这个配置把 docs 放在根路径,并关闭默认博客:
// docusaurus.config.js
export default {
title: '工程知识站',
url: 'https://docs.example.com',
baseUrl: '/',
organizationName: 'example-org',
projectName: 'engineering-docs',
onBrokenLinks: 'throw',
onDuplicateRoutes: 'throw',
presets: [
[
'classic',
{
docs: {
path: 'docs',
routeBasePath: '/',
sidebarPath: './sidebars.js'
},
blog: false,
theme: { customCss: './src/css/custom.css' }
}
]
],
i18n: { defaultLocale: 'zh-Hans', locales: ['zh-Hans', 'en'] }
}url 是站点的规范域名,baseUrl 是部署前缀,二者不能混成一项;routeBasePath: '/' 只是把 docs 路由搬到根,不会自动删除 src/pages/index.js。官方 routeBasePath 参考要求普通路径不带末尾斜杠,只有把文档放到站点根时使用 /。若某个文档再用 slug: / 充当首页,就要删除或改名已有首页文件,否则两个输入会映射到同一路由。Docusaurus 对重复路由默认只发出警告并继续构建,所以 CI 还要设置 onDuplicateRoutes: 'throw';onBrokenLinks: 'throw' 则阻断可检测的死链。
页面 URL 是否保留末尾斜杠由 Docusaurus 的 trailingSlash 与托管平台共同决定,VitePress 的 cleanUrls 也需要服务器把 /guide/config 映射到实际生成文件。不要仅凭本地开发服务器选择 URL 形态;应在目标静态托管上验证深层页面刷新、重定向次数、规范链接和 404 行为。VitePress 部署指南和 Docusaurus 部署指南都把服务器映射视为发布配置的一部分。
用正反实验证明路由不是装饰
先做正向实验。VitePress 下创建 docs/guide/index.md 和 docs/guide/config.md,在首页分别链接 ./guide/ 与 ./guide/config。运行 npm run docs:build 后,检查 .vitepress/dist/guide/index.html 和 .vitepress/dist/guide/config.html 是否存在,再用 preview 服务访问 /guide/。根据VitePress 路由文档,index.md 会变成目录首页,内部链接最好省略扩展名,让框架根据配置生成最终 URL。
再把环境变量设成子路径,重新构建:
$env:DOCS_BASE = '/handbook/'
npm run docs:build
Get-ChildItem docs/.vitepress/dist -Recurse -File | Select-Object -First 8 FullName
Select-String -Path docs/.vitepress/dist/index.html -Pattern '/handbook/'预期是 HTML、主题资源和站内链接都带有 /handbook/ 前缀;部署服务必须把这个目录映射到 /handbook/,而不是把它的内容再嵌入一个额外的 /handbook/handbook/。如果只改 Nginx location,不改 base,浏览器会请求根路径下的脚本和图片,常见证据是首页 HTML 返回 200,但 assets/*.js 返回 404,页面只剩静态骨架。反过来,如果站点部署在域名根路径却保留了 /handbook/,首页可能正常返回但所有绝对链接多出一级路径;这就是“开发环境正常、发布环境失败”的典型分叉。
Docusaurus 做同一组实验时,观察的是 url、baseUrl、docs 插件路由和 static/ 资产。把 baseUrl 改成 /handbook/ 后执行 npm run build,再用静态服务器把 build/ 挂到该前缀下;首页、docs 页面、主题资源和 favicon 都要从同一前缀解析。官方部署文档将生成的 build 目录作为静态发布输入,并给出 GitHub Pages 等平台的前缀配置入口。
反向实验更有价值:在 Docusaurus 的 docs 目录添加一个页面,写入 slug: /,但保留 src/pages/index.js,然后执行 npm run build。若沿用默认配置,Docusaurus 只会报告重复路由警告,站点仍可能构建成功;配置 onDuplicateRoutes: 'throw' 后,同一冲突才会让 CI 失败。删除旧首页或把文档 slug 改成明确路径,再次构建;如果成功,说明修复的是“输入到路由的唯一性”,不是简单压掉警告。路由实验的证据应包含 HTTP 状态、HTML 中的资源前缀、关键产物路径和死链输出,不能只看命令退出码。
内容模型决定主题和插件怎么接
VitePress 把 Markdown 当作一等输入。frontmatter 可以覆盖标题、描述、布局和部分主题行为;Markdown-it 扩展处理标题锚点、代码高亮、目录、容器和自定义插件;Vue 组件可在 Markdown 中直接使用,但组件在构建阶段会参与 SSR,所以访问 window、document 或仅浏览器存在的 API 时必须延迟到客户端。Using Vue in Markdown和SSR 兼容说明都把这个边界说得很清楚:构建时不存在浏览器对象,未做兼容处理的组件会让静态构建失败或产生服务端与客户端不一致。
因此 VitePress 的主题改造通常从 theme/index.ts 扩展默认主题开始,再通过 Layout.vue、组件注册和 CSS 变量调整页面;Markdown-it 插件适合改变文本解析,不适合承载复杂业务状态。需要从本地文件生成 API 索引时,可以使用 .data.js 或 .data.ts 数据加载器在构建时读取文件并序列化结果,但不要把运行时密钥或内部接口响应直接写进最终 JavaScript 包。数据加载文档说明了 loader 只在构建阶段执行,产物中的数据会进入客户端包。
Docusaurus 的 Markdown 是 MDX。普通 Markdown 仍然是主要内容,但你可以导入 React 组件、使用 JSX、插入 admonition 和代码块,并把插件提供的 remark/rehype 处理器接入内容管线。Markdown Features列出了这些扩展;它们带来的代价是页面不再只是纯文本,组件依赖、浏览器执行、包体和构建内存都进入发布链。VitePress 中的 Vue Demo 组件不是 Docusaurus 中可以直接复用的同名组件:前者依赖 Vue 组件和 VitePress 主题上下文,后者需要 React 组件、导入路径和 Docusaurus 的主题约定。
插件边界也不同。VitePress 通过 vite、vue、markdown.config 和自定义主题扩展,依赖 Vite 插件时要检查插件是否支持 SSR 与静态构建;Docusaurus 通过 plugins、themes、presets 组合能力,官方插件文档支持 npm 包、配置数组和本地插件。把分析脚本、搜索、OpenAPI 生成器或自定义页面接入 Docusaurus 时,应优先选择插件实例,让输入目录、路由前缀和插件 ID 形成稳定边界;不要让多个插件写入相同 URL。
i18n、版本文档与搜索要一起设计
多语言在 VitePress 里通常是目录和 locale 配置的组合。可以保留根语言文件,再增加 fr/、zh/ 等目录,并在 locales 中设置 label、lang、语言专属标题和主题配置。VitePress 国际化文档特别提醒:如果每种语言都放在独立目录,框架不会默认把 / 重定向到某一种语言,重定向、cookie 和默认语言策略需要部署层或自定义主题承担。翻译页面数量和路由数量会一起增长,目录、图片和搜索索引不能只翻译 UI 标签。
VitePress 默认主题可启用本地模糊全文搜索:
// .vitepress/config.mts
export default {
themeConfig: {
search: { provider: 'local' }
}
}它使用浏览器侧索引,部署简单,适合中小型文档站;内容量扩大后要关注首次下载的索引体积、中文分词体验和旧页面是否仍被打进索引。需要 Algolia 时,配置 appId、搜索用 apiKey 和 indexName,并确认爬虫只读取允许公开的 URL。VitePress 搜索参考同时列出了本地搜索、Algolia 以及社区插件入口;搜索密钥、爬虫配置和索引删除责任不能留在某位作者的个人账号里。
Docusaurus 的 i18n 按 locale 和插件保存翻译文件。Markdown/MDX 文档通常整体复制到 i18n/[locale]/docusaurus-plugin-content-docs,主题和 React 文本进入 JSON,官方国际化指南还提供 docusaurus write-translations 生成界面文案的入口。与 VitePress 的“平行目录”相比,Docusaurus 的翻译目录带有插件名称和版本层级,适合把 docs、blog、theme 的翻译责任分开,但迁移时不能只复制 Markdown。
Docusaurus 版本化是插件能力,不是给文件名加 v2。执行版本命令会生成 versions.json、版本目录和版本元数据;官方版本文档支持设置 lastVersion、onlyIncludeVersions、版本路径、banner 和 badge。版本数一多,构建时间、静态体积、搜索索引和翻译量都会按版本放大。先保留当前版本和一个受支持的历史版本,再把旧版本标成 unmaintained;不要让“所有历史版本都能访问”成为无限增长的默认策略。
VitePress 没有同等内置的版本文档数据模型,通常用目录、分支、构建矩阵或多站点配置实现。例如 v1/、v2/ 目录易懂,但会把侧栏、链接和翻译重复一份;用 Git 分支构建更清晰地表达版本事实,却需要托管层维护多个入口。这个差异会直接影响选型:如果版本文档是产品交付核心,Docusaurus 的插件模型减少自建元数据;如果主版本只有一条、目录结构就是团队知识树,VitePress 的透明文件模型更容易审查。
把项目接入变成可回滚的交付链
接入已有仓库时,先固定四个事实:源文件根目录、构建命令、产物目录和公开 URL 前缀。VitePress 适合放在 docs/,用 docs/.vitepress/config.mts 管理配置;Docusaurus 适合保留 docs/、src/pages/、static/ 和配置文件的约定,避免把一个已存在的前端应用目录硬套进 classic 模板。两者都应在 package.json 中保留独立脚本,CI 调用脚本而不是依赖开发者本地的全局 CLI。
一个接入检查脚本至少要验证构建产物和关键入口:
function Assert-Page($path) {
if (-not (Test-Path $path)) { throw "missing page: $path" }
}
# VitePress
npm run docs:build
Assert-Page 'docs/.vitepress/dist/index.html'
Assert-Page 'docs/.vitepress/dist/guide/index.html'
# Docusaurus
npm run build
Assert-Page 'build/index.html'
Assert-Page 'build/docs/intro/index.html'项目接入时不要把 node_modules、.vitepress/cache、.vitepress/dist、Docusaurus build 和临时搜索索引提交进仓库。锁文件必须提交;构建环境应使用 npm ci 或项目规定的等价锁定命令。若站点会从私有 npm registry 安装插件,CI 只注入短期读取凭证,日志不得打印完整 registry URL、token 或 .npmrc 内容。插件包是构建链的一部分,不能因为它只在“写文档”时运行就跳过依赖漏洞和许可证审查。
发布端还要把 HTML 与哈希资源分开缓存。内容哈希进入文件名的 JS、CSS 和图片可以使用较长的 max-age 与 immutable;HTML、搜索清单和仍会原地更新的固定文件名应允许重新验证,否则旧 HTML 会持续引用已清理的产物。MDN 的 Cache-Control 指南给出了这两类缓存模式。CDN 不应给 404、鉴权错误页或 SPA fallback 套用静态资源的长期规则,回滚前也要确认缓存键是否包含主机、路径前缀和查询参数。
迁移 Markdown 时,先用脚本统计 frontmatter 字段、相对链接、图片、代码片段、Vue/React 组件和自定义容器,再逐页处理。VitePress 的 base 会自动参与站内链接和静态资源处理;动态主题组件中的路径则可能需要 withBase。Docusaurus 的 baseUrl、docs routeBasePath、slug 和 static 资源有各自职责,不能把 VitePress 的绝对路径规则原样复制过去。旧路由要建立重定向表,旧搜索索引要重新抓取或删除,旧 CDN 产物要按缓存策略失效。
清理和回滚应先切换流量,再删除产物。VitePress 可删除 .vitepress/dist 和 .vitepress/cache,Docusaurus 可删除 build;这些动作不会修改源 Markdown。升级失败时锁文件和配置一起回退,再重新执行干净安装和构建;不要只替换 node_modules。如果新版本已经发布过,回滚还要恢复旧产物、旧 base/baseUrl、重定向规则和搜索索引,否则用户会看到旧页面但点击到新路由,或者旧路由返回空白页。
用构建证据定位故障,而不是猜配置
开发服务打不开时,先分为运行时、依赖解析和内容编译三类。Node 版本不满足时通常在 CLI 启动阶段失败;ESM 配置被 CommonJS 解析时会出现 require、export default 或模块类型错误;MDX/Markdown 语法或组件导入错误则多发生在特定页面构建。先执行 node --version、包管理器版本检查和框架版本检查,再执行单页最小构建,不要一上来删锁文件。
页面返回 200 但资源 404,优先比较部署前缀和 HTML 中的资源 URL:VitePress 看 base,Docusaurus 看 url、baseUrl 和静态资源位置。页面能打开但侧栏空白,VitePress 看 themeConfig.sidebar 的路径键和 items 结构,Docusaurus 看 sidebars.js 的 doc ID、目录文件名和 docs 插件实例。Docusaurus 早期版本迁移时,官方版本页面和升级文档是必要入口;不要把 v1 的主题或配置字段直接当作 v3 的兼容层。
页面构建成功但浏览器运行时报错,检查组件所在环境。VitePress 的主题和 Markdown 组件会经历服务端渲染与客户端接管,浏览器 API 应放在 onMounted 或等价客户端分支;Docusaurus 的 React 组件、swizzle 主题组件和客户端插件要检查是否把服务端不可用对象放进模块顶层。Docusaurus 还通过 Browser support 的 browserslist 影响转译程度,老浏览器兼容范围越宽,通常意味着更大的脚本和更高的构建成本。静态构建成功只能证明生成器完成,不代表每个交互组件在目标浏览器、移动端和严格 CSP 下都能运行。
搜索故障也要分层。页面未进入索引可能是构建输入被排除、语言目录没有被抓取或版本被 onlyIncludeVersions 排除;结果旧可能是静态索引、Algolia 索引、浏览器缓存或 CDN 缓存中的某一层未更新;结果泄露则要立刻从源文件、构建产物、爬虫入口和索引删除四处追查。不要只在搜索框里删关键词测试,要记录搜索服务实际抓取的 URL 和可见范围。
按内容结构和团队约束做选型
如果团队有一条主版本、内容主要是 Markdown、站点要贴近源码目录、希望快速构建并把产物交给任意静态服务器,VitePress 通常更自然。文件到 URL 的映射透明,主题默认面向技术文档,本地搜索不需要额外服务;代价是多版本、复杂内容插件和跨语言部署策略需要你自己建立约定。Vue 生态的组件能力很强,但 React 组件不能直接迁入,SSR 兼容和 Vite 插件供应链要由团队维护。
如果产品文档、博客、营销页面和多个文档版本需要统一站点,或者团队已经有 React 组件与插件,Docusaurus 的内容插件模型更能表达长期结构。docs、blog、pages、i18n、versioning、theme 和 plugin 可以有清晰 owner;代价是配置层更厚,构建和依赖体量更大,版本与翻译会放大产物和索引成本,MDX 组件也会让内容审核和安全边界更复杂。
选型不要做单项功能打分,而要看迁移和治理总账:
| 决策问题 | 更偏向 VitePress 的信号 | 更偏向 Docusaurus 的信号 |
|---|---|---|
| 内容来源 | 单一 Markdown 树贴近代码仓库 | docs、blog、pages、插件实例并存 |
| 路由控制 | 文件路径就是主要事实来源 | 插件、版本、slug 和侧栏共同决定路由 |
| 组件生态 | Vue 组件与 Vite 生态 | React 组件、主题 swizzle 与插件生态 |
| 版本文档 | 主版本为主,历史目录可控 | 多个产品版本需要独立 banner、路径和索引 |
| 搜索 | 浏览器本地索引足够 | 需要 Algolia 或多内容源统一搜索 |
| 构建治理 | 追求轻量、透明、低运行时 | 接受更厚的构建链换取结构能力 |
表格只能帮助你发现分歧,最终还要拿真实内容做迁移试验:抽取一组带图片、组件、内部链接、侧栏、翻译和历史版本的代表页面,分别生成产物,比较 URL 变化、构建时长、索引体积、包体、审核工作量和回滚步骤。只用首页和一篇纯 Markdown 文档做选择,几乎一定会低估迁移成本。
权限、敏感数据、容量与长期治理
站点源码和构建产物的权限要分开。写作人员可以提交 Markdown 和图片,但不应默认拥有生产域名、搜索管理后台、CDN 清缓存或部署凭证;构建机器人需要读仓库、读私有依赖和写制品存储,通常不需要读数据库或业务密钥。Docusaurus 配置在 Node 中运行,VitePress 数据 loader 也在 Node 中运行,所以任何放进配置或 loader 的环境变量都可能在构建日志、错误堆栈或序列化产物中暴露。
敏感数据检查至少覆盖四个位置:Markdown 及 frontmatter、static/ 或 public/、生成的 HTML/JS、搜索索引。真实账号、内部路径、临时下载链接、访问 token、私有接口响应和未公开截图都不能用“页面没有挂导航”来保护。搜索服务通常会复制标题、摘要、URL 和正文片段;删除源码后,索引、缓存和旧构建目录仍可能保留一段时间,必须把删除动作纳入发布清单。
SVG 还会改变组件信任边界。Docusaurus 支持把导入的 SVG 转成 React 组件,VitePress 主题也可能通过插件或内联标记把 SVG 放进页面 DOM;这与 <img src="...svg"> 的受限图片上下文不是一回事。只有仓库内经过审查的 SVG 才能作为组件导入,外部上传文件应先经过能解析 SVG 的清洗器或转成位图,再进入 static/public。浏览器没有弹窗不能证明安全,发布端仍要限制 CSP 的 object-src、img-src,并返回 image/svg+xml 与 X-Content-Type-Options: nosniff。
容量问题不只来自图片。Docusaurus 的版本数、locale 数、MDX 组件依赖和搜索爬取量会乘法增长;VitePress 的本地搜索索引、数据 loader 序列化结果、代码高亮和自定义主题同样会增加构建内存与浏览器下载。建议在 CI 记录构建耗时、峰值内存、产物字节数、HTML 页面数、JavaScript 体积和搜索索引体积,观察它们是否随页面、语言和版本数量单调增长。演示阈值可以用于预警,生产阈值应来自当前构建机、发布窗口和用户体验预算。
插件治理要有 allowlist、owner、锁版本、升级窗口和撤销路径。VitePress 的 Vite/Markdown-it/Vue 插件可以在构建阶段执行任意 Node 逻辑;Docusaurus 的插件和 remark/rehype 包也处在同一信任边界。安装插件前检查发布包、依赖树、许可证、维护状态、是否读取文件系统、是否发起网络请求、是否能在 SSR 和静态构建中稳定工作。升级失败时保留旧锁文件和旧构建产物,先在分支执行代表页面构建,再切换域名或 CDN 流量。
长期维护还需要一个清晰的“谁拥有哪一层”约定:内容 owner 负责 Markdown、frontmatter 和链接;主题 owner 负责组件、CSS 和浏览器兼容;构建 owner 负责 Node、包管理器、插件版本、CI 和制品;搜索 owner 负责抓取、索引删除和语言策略;发布 owner 负责域名、前缀、缓存和回滚。每次框架升级都应检查官方安装入口、Node 支持、配置字段、插件 API、路由/base 行为、搜索和弃用说明。VitePress 的迁移指南明确提醒 VuePress 与 VitePress 在 base、主题配置和组件模型上并非无缝兼容;Docusaurus 站点则应从当前版本的升级入口和对应版本文档核对,不要依据旧博客文章推断兼容性。
最后,用一次完整回滚演练闭环:在临时域名或静态预览目录发布新产物,验证首页、深层路由、图片、搜索、语言和历史版本;故意把前缀改错,确认监控或页面检查能发现资源 404;恢复旧产物和旧配置,清理新索引与 CDN 缓存,再检查旧路由仍然可达。这样选出的不是某个“功能更多”的框架,而是一条团队能够解释、验证、升级和撤销的站点交付链。
