VuePress 2 站点写作工具链:从 Markdown 到可回滚的静态站点
页面能在开发机打开,发布后却变成空壳
站点写作者最容易遇到一种令人困惑的故障:npm run dev 能打开页面,换成静态服务器后图片 404;文章中的组件在浏览器里能显示,npm run build 却在 SSR 阶段报 window is not defined;旧页面虽然删除了,搜索索引和 CDN 里仍然能搜到;升级一个主题插件后,原本可用的 themeConfig 不再生效。它们看起来分散,实际都发生在同一条链路上:源文件先被 VuePress 识别为页面,再经过 Markdown 解析、Vue SFC 转换、主题组装、路由生成和 bundler 构建,最后落成静态 HTML、JavaScript、CSS 与图片。
VuePress 不是“把 Markdown 复制到服务器”的脚本。官方 Introduction 将它定义为以 Markdown 为中心的静态站点生成器:页面由 Markdown 变成 HTML,再作为 Vue 组件模板处理;站点运行时又是由 Vue 和 Vue Router 驱动的 SPA。这个模型解释了两个重要事实:文章目录会影响初始路由,Markdown 中的 Vue 模板会进入构建编译器,浏览器里成功不代表 SSR 和静态部署一定成功。
本仓库的可迁移形状是 docs 作为源目录,docs/.vuepress/config.ts 管站点配置,docs/.vuepress/client.ts 管浏览器端增强,docs/.vuepress/public 放不会被 Markdown import 追踪的公共文件,docs/article 存文章。实际项目可能把源目录换成 site、把产物换到 dist 或使用另一套主题,但每次换名都应该让命令、配置、链接和构建产物保持同一口径。
先记住一条排障原则:看到页面问题时,不要只盯浏览器截图。先确认输入文件是否被 pagePatterns 收集,再看 frontmatter 是否改变路由或布局,随后检查 Markdown 是否生成了合法 Vue 模板,最后才检查主题、bundler、base 和静态服务器。这样才能把“页面不对”拆成可证据化的故障。
从零创建一个能跑的 VuePress 2 项目
VuePress 2 仍处在 RC 发行线,配置和 API 可能在 RC 升级时发生破坏性变化。这里还有一个不能忽略的版本陷阱:入门页仍写着 Node.js 20.9.0 或更高版本,而当前 vuepress@next 包的官方 package.json已经通过 engines.node 要求 Node.js 22.18.0 或更高版本。真正安装时要同时检查目标版本的包元数据、主题和插件的 peer dependency,不能只抄网页上的最低版本。生产项目还应锁定依赖和 lockfile,并在升级前阅读迁移指南和变更记录。
新机器先验证 Node 和包管理器,而不是直接执行全局安装。全局 VuePress 会绕过项目 lockfile,也会让 CI 和同事得到不同的插件树。一个最小站点可以由官方 Getting Started 的 CLI 或手工目录开始;团队项目通常更适合手工显式安装 bundler 与主题:
node --version
npm --version
npm view vuepress@next engines peerDependencies
npm init -y
npm install --save-dev vuepress@next @vuepress/bundler-vite@next @vuepress/theme-default@next vue
mkdir docs
mkdir docs/.vuepress
npm ls vuepress @vuepress/bundler-vite @vuepress/theme-default vue@next 是寻找 VuePress 2 包的发行标签,不是团队版本策略;首次安装结束后,lockfile 必须保存解析到的精确版本。核心、bundler、默认主题和插件不保证共享同一个 RC 尾号,兼容关系应以各包声明的 peer dependency 和一次干净构建为准。核心包提供应用和 CLI,@vuepress/bundler-vite 提供 Vite 适配,默认主题提供布局和主题能力,vue 是 peer dependency。若使用 pnpm,要显式满足 peer dependency;若使用 Yarn 2+,VuePress 安装说明要求在 .yarnrc.yml 中设置 nodeLinker: node-modules。第一次安装失败时,先保留完整报错,再核对 Node、包管理器、lockfile、registry 和 peer dependency。
配置文件可以使用 config.ts、config.js 或 config.mjs。下面这个文件足以建立一个可复现入口:
import { viteBundler } from "@vuepress/bundler-vite";
import { defaultTheme } from "@vuepress/theme-default";
import { defineUserConfig } from "vuepress";
export default defineUserConfig({
lang: "zh-CN",
title: "工程知识站",
description: "可验证的工程文档",
bundler: viteBundler(),
theme: defaultTheme({
navbar: [{ text: "首页", link: "/" }],
}),
});再放一个 docs/README.md,并在 package.json 的 scripts 中加入 docs:dev 和 docs:build。官方入门页的命令形式是 vuepress dev docs 与 vuepress build docs;调用项目本地二进制可以避免 PATH 中的旧版本抢先执行:
{
"scripts": {
"docs:dev": "vuepress dev docs",
"docs:build": "vuepress build docs"
}
}运行 npm run docs:dev 后,预期是得到一个带热更新的开发服务器;运行 npm run docs:build 后,预期是在 .vuepress/dist 看到静态产物,除非配置中的 dest 改写了输出目录。端口被占用时可以换端口,但不要把“能打开某个 URL”当成验证完成:还要点击一篇子路径文章、刷新该 URL、打开图片和查看浏览器控制台。
如果初始化失败,证据顺序应当是:node --version 是否满足要求,npm ls vuepress @vuepress/bundler-vite vue 是否出现重复或缺失,配置是否导入了与 bundler 对应的包,最后再查看完整构建栈。VuePress v1 的 module.exports、字符串主题名和 themeConfig 不能直接搬进 v2;v2 需要直接导入 defaultTheme,插件也要导入函数或对象。这个变化在 v1 到 v2 迁移页 有明确对照。
目录、配置字段和它们改变的运行行为
一个长期维护的站点,目录不是装饰,而是输入契约。推荐让文章、静态资源、客户端增强和临时产物分开:
site-project/
├─ docs/
│ ├─ README.md
│ ├─ article/
│ │ └─ tool-chain.md
│ └─ .vuepress/
│ ├─ config.ts
│ ├─ client.ts
│ ├─ public/
│ │ └─ images/
│ ├─ components/
│ ├─ styles/
│ ├─ .temp/
│ ├─ .cache/
│ └─ dist/
├─ package.json
└─ package-lock.jsondocs 是 source directory,只有被页面模式收集的 Markdown 会生成页面;.vuepress 是 VuePress 自己的配置和扩展区。pagePatterns 的匹配基准是 source directory,默认行为会广泛收集其中的 Markdown。不同 RC 的默认排除项曾有变化,因此草稿、内部资料或带凭证的 Markdown 必须由项目显式排除,不能因为它们没有导航入口就假设不会被处理。Page 指南还说明 README.md 和 index.md 会映射到同一目录入口;同时保留二者时,应通过页面模式排除一个,避免重复路由。
配置字段应按影响面理解。lang 改变输出 HTML 的语言属性,title 和 description 进入站点标题与 meta,head 注入额外标签,base 决定部署在非根路径时的 URL 前缀,dest 改变产物位置,public 改变公共文件源目录,temp 和 cache 影响中间文件与重建速度,pagePatterns 决定哪些文件成为页面,permalinkPattern 改变 URL 生成规则,debug 打开调试信息。配置参考见 Config Reference。
例如项目准备部署到静态服务器的 /knowledge/ 子路径,配置应明确写出首尾斜杠:
export default defineUserConfig({
base: "/knowledge/",
pagePatterns: ["**/*.md", "!drafts/**", "!internal/**", "!.vuepress"],
debug: process.env.VUEPRESS_DEBUG === "1",
});这里故意保留 dest、public、temp 和 cache 的默认值,它们分别位于 source directory 下的 .vuepress/dist、.vuepress/public、.vuepress/.temp 和 .vuepress/.cache。一旦显式填写,普通相对字符串会受命令工作目录影响;团队若确实需要移出默认目录,应在配置中用 path.resolve() 生成绝对路径,并让 CI、清理脚本和部署脚本一起改。base 必须以 / 开头和结束,配置参考说明它会参与多数 URL 处理,但 head 标签属性中的路径不会自动补前缀。把 base 写成 knowledge 或 /knowledge,常见结果是首页似乎正常,深层路由、favicon 或脚本却 404。
配置文件本身也可以作为一个插件使用,因此把所有逻辑塞进 config.ts 很方便,但不利于审查。当一个规则需要独立测试、需要 client config 或需要复用时,再把它抽成插件;否则短配置保持在一个入口更容易追踪。不要在多个配置文件中同时定义同名字段,也不要让环境变量随意改变路由和资源前缀,生产构建应该输出一份可审计的最终配置。
主题、插件和 Markdown 是三种不同的扩展面
主题决定页面布局、导航、侧栏、目录、样式和主题组件;插件是在初始化、页面准备、开发和构建阶段挂入能力的扩展点;Markdown 配置决定 markdown-it、frontmatter、标题锚点、链接、代码块和 Vue SFC 转换。三者都能影响最终页面,却不应混成“装个插件就行”。官方 Theme 和 Plugin 文档分别定义了它们的职责。
主题采用函数调用,配置直接传给主题:
import { defaultTheme } from "@vuepress/theme-default";
import { searchPlugin } from "@vuepress/plugin-search";
import { defineUserConfig } from "vuepress";
export default defineUserConfig({
theme: defaultTheme({
logo: "/images/site/logo.svg",
navbar: [
{ text: "首页", link: "/" },
{ text: "工具效率", link: "/article/tool-efficiency/" },
],
}),
plugins: [searchPlugin({}),],
});这段示例表达的是 v2 的扩展方式,搜索插件的实际包名、版本和配置要以项目选择的主题及插件官方文档为准;本仓库使用的是另一套主题和搜索插件,不能把 @vuepress/plugin-search 这一行原样塞入现有配置。插件通常只应实例化一次,除非该插件明确支持多实例;官方插件页还提醒,多数插件重复使用时只有最后一个生效。安装插件后,先用 npm ls 确认它与 VuePress 核心、主题和 bundler 的 peer dependency,再构建一篇最小页面。
官方插件 API 的生命周期分成几个阶段:初始化时处理 extendsMarkdownOptions、extendsMarkdown、extendsPageOptions、extendsPage 和 onInitialized;准备文件时处理 client config 和 onPrepared;dev/build 阶段处理 bundler options、alias、define、监听和生成钩子。这个顺序很重要:在页面已经生成以后改 Markdown 解析规则,往往不会得到想要的结果;在插件里注入浏览器组件,则要通过 clientConfigFile 或单独的 .vuepress/client.ts。
Markdown 默认支持 GFM 表格、删除线、标题锚点、内部链接转换、代码块标记、目录和 Vue 模板语法。官方 Markdown 指南 说明,内部 Markdown 链接会转换为 RouteLink,相对链接比硬编码绝对链接更能适应目录移动和多语言路径。代码块的语言、标题、行号、行高亮和 v-pre 都会影响构建产物;把带 {{ }} 的配置示例放进普通代码块时,应让默认 v-pre 阻止它被 Vue 当成表达式。
Markdown 文件最终会变成 Vue SFC。普通正文进入 template,单个 <script> 和 <style> 会作为 SFC 块处理。这样可以在文章里嵌入小组件,但也带来 SSR 风险:组件在 setup() 阶段访问 window、document 或浏览器尺寸,构建时没有 DOM 就会失败。第三方 Web Component 标签还可能被 Vue 当成未知组件,需要在 bundler 的 compiler options 中声明 isCustomElement。这不是 Vue 组件教程,而是站点构建边界:每个组件都必须说明它在 Node SSR、浏览器 hydration 和静态输出中的行为。
frontmatter、Markdown 文件和公共图片怎样共同决定页面
frontmatter 必须位于 Markdown 文件顶部,用 YAML 两条 --- 包住。站点配置提供全局默认值,页面 frontmatter 可以覆盖当前页面的 lang、title、description、head、layout、permalink 等字段;VuePress frontmatter 参考只定义核心字段,主题和项目脚本还可以消费自己的字段。下面的 title、description 和 permalink 属于 VuePress 核心语义,article、timeline、index、sitemap 则属于所选主题或插件的契约,换成默认主题时不能假设它们仍然有效:
---
title: 构建链路排障手册
description: 记录构建输入、路由和静态产物之间的证据关系。
author: 工程文档组
article: true
timeline: false
index: true
sitemap: true
permalink: /article/tool-chain.html
---
## 页面标题
正文从二级标题开始,站点主题负责显示页面标题。permalink 是稳定链接的承诺,不是随手改名的字段;一旦被外部文档、搜索索引或书签使用,修改它就需要旧路由重定向或兼容页面。permalinkPattern 可以按文件路径生成规则化地址,但日期变量会依赖 frontmatter、文件名或目录名,容易把动态时间信息意外写进公开 URL。路由规则和 frontmatter 参考分别见 Page 与 Frontmatter Reference。
文章内图片有两条常见路径。与 Markdown 放在一起、且只被该文章引用的图片可以用相对路径,例如 ;共享 logo、favicon、海报和不由 Markdown import 追踪的资源则放到 .vuepress/public,再用 /images/site/logo.svg 这类以根路径开头的 URL。官方 Assets 说明 public 下的文件会被复制到生成目录根部。
本仓库的海报引用采用 public 绝对路径:/images/articles/tool-efficiency/site-writing-vuepress-22.svg。部署到 /knowledge/ 后,页面层仍应引用 /images/...,让 VuePress 处理 base;自定义 Vue 组件动态拼接资源时,要使用 withBase:
<script setup lang="ts">
import { withBase } from "vuepress/client";
const props = defineProps<{ source: string }>();
</script>
<template>
<img :src="withBase(props.source)" alt="文档资源预览" />
</template>Assets 指南说明,Vite bundler 会为 Markdown 中以 / 开头的 public 图片补上 base,动态绑定仍要使用 Client API 的 withBase。Webpack bundler 不会默认完成同样处理:可以设置 markdown.assets.absolutePathPrependBase: true,或者统一使用 withBase。正向实验是把 base 临时改成 /knowledge/,构建后用静态服务器访问 /knowledge/、文章页和海报;反向实验是把动态 <img> 写成硬编码 /images/...,或在 Webpack 下关闭上述选项,观察请求是否错误地落到域名根目录。证据应来自浏览器 Network 的实际请求 URL,而不是只看源码。
初始化、开发服务器和静态构建的最小闭环
开发服务器适合快速反馈,它会监听 Markdown、配置和组件变化并热更新;生产构建则要完成页面预渲染、资源打包和输出目录清理。两者使用同一份配置,却可能在 SSR、懒加载、路由 fallback、环境变量和文件大小上表现不同。因此每次增加主题插件、Vue 组件或路径配置,至少要做一次开发访问和一次静态构建。
加入 docs/article/tool-chain.md 后,可以用下面的闭环验证输入是否真的进入站点:
npm run docs:dev
# 浏览器中检查:首页 -> 文章页 -> 海报 -> 页面刷新 -> 外部链接
# 停止开发服务器后再执行,或在另一个终端运行
npm run docs:build
Get-ChildItem docs/.vuepress/dist -Recurse -File | Select-Object -First 20
Select-String -Path docs/.vuepress/dist/**/*.html -Pattern "构建链路排障手册"在 Unix CI 中可用 find、grep 替换 PowerShell 命令。预期结果不是“目录非空”,而是:文章标题在 HTML 中出现,文章路由有对应的 HTML 或主题约定的入口,海报文件存在,资源引用指向正确 base,构建返回 0。若构建返回 0 但文章不在产物中,优先检查 pagePatterns、文件扩展名、排除规则和 source directory;若文章存在但访问 404,再检查路由尾斜杠、静态服务器 fallback 和 base。
浏览器刷新深层路由是一个很有价值的反向实验。SPA 开发服务器通常会把未知路径回退到入口 HTML,所以 /article/tool-chain.html 在 dev 中能开;普通静态服务器如果没有对应文件或 rewrite 规则,刷新可能返回 404。VuePress 生成的静态产物应优先按部署平台支持的目录结构托管,不能用开发服务器的 fallback 行为替代发布验证。部署入口可以参考官方 Deployment,但平台的 rewrite、缓存和权限仍要在目标环境单独验证。
调试时可打开配置中的 debug: true;VuePress 使用 debug 包,POSIX shell 可执行 DEBUG='vuepress*' npm run docs:build,PowerShell 则先设置 $env:DEBUG = 'vuepress*'。日志要和构建命令、Node 版本、实际解析包版本一起保存。构建警告不能一概忽略:缺少语法高亮器、动态 import 无法解析、图片路径大小写不一致、chunk 超大和 hydration mismatch 可能都先以 warning 出现,随后才在特定平台变成故障。只看退出码会让这类漂移积累到发布时才暴露。
组件注册、路由和调试边界
全局组件注册适合站点级、多个页面都会使用且 API 稳定的组件,例如文章海报、提示块或统一的资源预览。局部组件适合单篇实验,避免把临时逻辑写入所有页面的 SSR 包。VuePress 2 的 client config 文件可以放在 .vuepress/client.ts,使用 defineClientConfig 提供 enhance、setup、layouts 和 rootComponents。官方 Usage of Client Config 给出了直接调用 app.component 注册全局组件的方式:
import { defineClientConfig } from "vuepress/client";
import AssetPreview from "./components/AssetPreview.vue";
export default defineClientConfig({
enhance({ app }) {
app.component("AssetPreview", AssetPreview);
},
setup() {
// 只放与客户端生命周期相关的逻辑;不要在这里读取服务端不存在的 DOM。
},
});若组件只在客户端有意义,可以包在 <ClientOnly> 中,或者把 DOM 访问移入 onMounted。这会改变 SSR HTML:构建产物中可能只有占位内容,搜索引擎和无 JavaScript 浏览器看不到组件内部文字。对知识站来说,文章的关键结论和图片替代文本应留在 Markdown 或 SSR 可生成部分,交互控件只能承担辅助阅读。
路由通常由 Markdown 文件的相对路径生成:根目录 README.md 对应根路由,目录内的 README.md 对应该目录入口,普通 Markdown 则对应其文件路径。内部链接使用相对 .md 路径更适合在源文件中审阅,也能由 VuePress 转成 RouteLink;外部链接会带上新窗口和 noopener noreferrer 属性。目录名中的 :、+ 等字符可能有 vue-router 的特殊含义,官方 Page 指南建议避免使用。
自定义 router 只在真正需要增强导航行为时使用。若只是为了把旧 URL 指向新文章,应在主题或重定向插件中维护映射,并为旧路由写一个可访问的验证;若直接在客户端钩子里无条件 router.replace,可能造成首屏闪烁、循环重定向或静态构建与浏览器行为不一致。路由迁移要同时检查 HTML 文件、链接、搜索索引、CDN 缓存和旧书签。
组件调试应分三层。先在 Markdown 中放一个不依赖浏览器 API 的静态组件,确认全局注册和模板解析;再加入 onMounted,确认 hydration 后状态变化;最后才接入网络、存储或第三方 SDK,并让这些代码在 SSR 阶段不执行。若第一层就失败,多半是导入路径、组件名或 client config 未被加载;若静态 HTML 正常、浏览器控制台报错,多半是 hydration、浏览器 API 或数据时序;若开发正常、构建失败,则重点看 SSR 可用性和 bundler 编译选项。
Vite、Webpack 与 Markdown 扩展的选择代价
VuePress 支持 Vite 和 Webpack 两种 bundler,官方 Bundler 说明核心包不会替你安装 bundler,需要显式选择。Vite 通常提供更快的开发反馈和较简单的现代前端依赖链;Webpack 适合已有 webpack loader、复杂 legacy 兼容或团队已经有成熟 webpack 配置的站点。选择不是性能口号,而是看主题、插件、sass、图片处理、浏览器目标和现有构建资产能否在同一条链上稳定运行。
切换到 Webpack 时,安装 @vuepress/bundler-webpack,并把 postcss、sass、scss、chainWebpack 和 configureWebpack 这类配置放进 webpackBundler() 的选项。v2 不再把 v1 的 webpack 字段平铺在顶层。官方 Webpack 参考还提示,默认主题使用 Sass,pnpm 项目可能需要显式安装 sass-loader 等 peer dependency。切换后应以干净依赖重新执行 dev、build 和页面访问,不要只看热更新成功。
Markdown 扩展也会改变构建成本和安全面。标题锚点、链接转换、目录、代码高亮和 import-code 都在 Node 侧解析;代码高亮插件会增大语言包和构建时间,import-code 会让页面依赖外部源码文件,文件移动或路径大小写变化会导致构建失败。扩展 Markdown 时优先使用官方内置能力和锁定版本的插件,并记录它读取的字段、生成的 HTML、失败退出码和升级影响。
正向实验可以加入一段带标题和行高亮的配置代码,构建后检查 HTML 是否有代码标题、目标行标记和正确语言 class;反向实验把 import-code 指向不存在的文件,预期构建返回非零并给出文件路径。若反向实验仍然成功,说明该语法可能被当作普通文本或页面没有被收集,这不是“构建更健壮”,而是测试没有击中真实解析链。
复杂站点可以通过 markdown 配置禁用或调整内置插件,但官方配置参考对 markdown.frontmatter、markdown.component、markdown.sfc、markdown.assets 等选项都提醒需要理解其作用后再改。尤其不要为了绕过一篇坏文章而全局关闭 frontmatter、链接或 SFC;更好的处理是修复输入、隔离页面模式,或把特殊内容移到可测试的组件中。
失败证据、项目接入和清理回滚
一个能交接的 VuePress 项目,故障记录至少包含“现象、第一证据、原因、修复、再验证”五个动作。例如图片在开发机正常、子路径发布后 404:第一证据是浏览器 Network 中请求是否缺少 /knowledge/,然后确认 base 是否首尾带斜杠、资源是否来自 public、bundler 是 Vite 还是 Webpack,再用 withBase 或相对路径修复,最后从干净产物重新检查页面。
文章在构建中消失:先执行 npm ls vuepress 并确认实际 source directory,再打印或审阅 pagePatterns,检查路径是否被 !drafts/**、!internal/** 排除,检查 frontmatter 是否 YAML 解析失败。不要先修改导航。导航没挂载的页面仍然可能是合法页面,导航挂载的页面也可能因为构建规则没有进入产物。
组件只在开发服务器出现:比较 dev 和 build 的 HTML,打开构建日志和浏览器控制台,检查组件是否依赖 window、document、随机数、当前时间或异步请求。把浏览器 API 移到 onMounted,把不稳定数据改成显式 props,或者使用 <ClientOnly> 并接受 SSR 内容缺失;修复后重新构建并刷新静态文件,而不是保留旧的 JS chunk。
项目接入时建议把站点当作一个独立构建目标:文章目录、站点配置、主题插件、锁文件和构建脚本由同一个 owner 审查;业务代码可以在 monorepo 其他目录,但不得从业务构建中隐式读取未锁定的文章或图片。CI 至少安装锁定依赖、执行文章检查、执行 vuepress build、检查关键 URL 和关键资源,并把产物作为带提交标识的制品。部署脚本、云端 CDN 和搜索索引属于相邻系统,接入时要交代它们消费的是哪个目录和哪个版本的产物。
清理开发实验时,不要删掉共享资源或误删源文章。安全的顺序是停开发服务器,删除 .vuepress/.temp 和 .vuepress/.cache,按构建策略删除 .vuepress/dist,确认 lockfile 没被改写,再从干净目录安装和构建。只想卸载一个插件时用包管理器更新 package.json 和 lockfile,并同步移除配置导入;不要手动删 node_modules 下的单个目录来“修复”依赖树。
npm run docs:build
Remove-Item -Recurse -Force -ErrorAction SilentlyContinue docs/.vuepress/.temp, docs/.vuepress/.cache
Remove-Item -Recurse -Force -ErrorAction SilentlyContinue docs/.vuepress/dist
npm ci
npm run docs:build这个清理实验假定项目使用 npm 和 package-lock.json;pnpm 或 Yarn 项目应使用各自的 frozen-lockfile 安装命令,不能混用 lockfile。预期结果是缓存清空后仍能从锁定依赖构建出等价页面,文章标题、路由和图片请求不变。反向做法是只删除一个传递依赖目录后继续构建,可能得到“本机能跑、CI 不能跑”的假象;真正的回滚应恢复同一版本的 package.json、lockfile、配置、主题插件和资源,再做一次干净构建。若静态产物已发布,回滚还要切换制品引用、清理错误 CDN 缓存,并保留旧路由的重定向策略。
架构选型:什么时候 VuePress 合适,什么时候应换工具
VuePress 适合 Markdown 是主要事实源、页面数量可管理、需要 Vue 组件增强、希望生成静态产物并由 Git 评审的知识站。它把内容和站点运行时放在同一个工程里,适合工程文档、API 说明、内部知识库和版本化手册。它不自动提供内容审批、细粒度读者权限、在线协作编辑、全文搜索服务或动态数据权限;这些要求必须由外围系统补齐,或者选择更贴合的文档平台。
VitePress 也采用 Markdown 和 Vue,启动与构建体验接近,迁移时仍需核对 frontmatter、主题 API、插件、组件注册、路由、base 和搜索。Docusaurus 更强调 React 生态和版本化文档能力,不能只搬 Markdown 文件就认为迁移完成。决定是否替换时,应比较真实页面的 SSR/CSR 行为、主题可替换性、插件维护、搜索索引、部署平台、团队技能和迁移回滚成本,而不是比较首页截图。
bundler 的选择应由依赖事实驱动:依赖现代 Vite 插件且没有 legacy loader 时优先试 Vite;已经有可靠 webpack loader、需要旧浏览器转译或主题依赖 Webpack 时保留 Webpack。默认主题和插件版本要共同测试;“核心包能安装”不证明主题能构建。对于一个追求稳定的知识站,少量成熟插件和清晰的自定义组件通常比大量社区插件更容易治理。
VuePress 2 RC 的平台边界也要明确:目标包的 engines.node、Vue 3 peer dependency、bundler、主题和插件的兼容矩阵必须写进项目升级记录;RC 版本不能被描述成配置 API 永远稳定。v1 的字符串主题、themeConfig、顶层 Webpack 配置、markdown.extendMarkdown 和旧 frontmatter 字段在 v2 有迁移变化或已移除,迁移页提供了具体替代方式。升级时以锁文件为基线,先在隔离分支构建全部页面,再审查路由、图片、主题组件、搜索索引和输出体积。
权限、敏感数据、容量、成本与长期治理
站点构建通常不需要生产数据库或云管理员权限。CI 只应读取源仓库、安装允许的依赖、写入临时目录并上传已审查制品;部署阶段使用专门的发布身份,权限限定为目标存储和 CDN 的发布操作。不要把云凭证、SSH 目录、私有 registry token 或未脱敏日志放进 public,也不要把真实内网地址、账号和连接串塞进 Markdown 示例。构建日志可能回显环境变量和插件参数,CI 应默认遮蔽敏感值。
第三方主题和插件是构建期代码执行入口。准入时检查维护状态、依赖许可证、发布包内容、安装脚本、peer dependency 和权限需求;升级时锁定版本并保留上一版 lockfile、构建产物和回滚命令。搜索插件可能把草稿、归档页或受限内容编入索引,pagePatterns、主题索引配置和部署产物必须一起检查。删除源 Markdown 不代表浏览器缓存、CDN、搜索索引和旧静态目录已经清除。
容量问题首先表现为构建时间、峰值内存、输出体积和开发热更新延迟。要记录页面数、Markdown 字符量、代码高亮语言数、图片总字节、最大 chunk、冷缓存与热缓存耗时。大量代码高亮、客户端组件、全量预取和未压缩图片会同时增加构建和访问成本;官方配置中的 prefetch 行为对小站有益,但页面很多时会制造额外请求。应依据访问趋势和构建基线决定是否关闭全量 prefetch、拆分主题能力或把大图片改成合理尺寸。
一个实用的容量验收可以写成趋势不变量:连续三次干净构建中,页面数量与源文件变化一致,未修改文章的路由集合不变,构建峰值内存没有随缓存轮次单调上升,公共资源总大小的增长能归因到具体变更,失败构建不会覆盖上一次可发布制品。示例中的阈值由项目基线决定,不要把某台机器上的秒数当成普遍标准。
团队长期维护至少需要四个角色动作:内容 owner 负责页面事实,站点 owner 负责主题、插件、路由和构建,安全 owner 负责依赖、敏感数据和发布身份,发布 owner 负责制品、缓存、回滚和旧路由。新文章应由内容变更触发构建;主题或插件升级应触发全站构建和关键页面浏览;base、public 或 permalink 改动应触发路径与旧链接检查。
升级窗口中先创建一份可回退的依赖快照,再逐项升级 VuePress 核心、bundler、主题、插件和 Node。每次只改变一个主要变量,保存构建日志和产物差异;遇到配置字段弃用时优先按照官方迁移页改为 v2 API,不要用兼容别名拖延。若升级失败,回到旧 lockfile 和旧配置构建,确认旧制品可发布,再决定拆分升级还是替换工具。
最终,VuePress 的价值不在于“Markdown 很快变成网页”,而在于它把文档源、页面模型、主题扩展、客户端行为和静态制品放进一条能被 Git、CI 和浏览器共同检查的链路。只要每个配置字段都有运行影响,每个插件都有 owner,每个资源都有路径证据,每次升级都有干净构建和回滚制品,站点才不会在发布后才告诉团队它其实依赖了某个人的机器状态。
