VuePress多侧边栏配置:路径匹配原理与多模块实践 如果你的 VuePress 站点只有一个模块侧边栏直接排一个数组就能用这个阶段大家都过得挺舒坦。但一旦文档拆成“使用指南”“API 参考”“更新日志”这种多模块结构再让所有页面共用同一份侧边栏读者在 API 页面里却看到使用指南的目录体验就会非常割裂。我上半年重构一个文档站时正好撞上这个问题——三个模块、近七十个页面每个模块的目录结构完全不同但打开任何一个页面侧边栏都是同一份。这个问题的本质其实是配置方式没选对。VuePress 的侧边栏配置走的是“按路径前缀匹配”的机制单一数组只是把同一套导航塞给了所有页面。想要实现多个侧边栏就得把 sidebar 从数组换成对象针对不同目录配置不同的导航内容。这篇文章就围绕这个核心机制展开我会先讲清楚匹配原理再给出常见的多侧边栏组织方式、目录结构怎么配合、以及后期维护和踩坑记录。内容以 VuePress v1 为例v2 的配置结构大体思路一致主要差异在主题配置项的位置理解了原理之后迁移很轻松。适合正在搭建多模块文档站、博客或者组件库文档的开发者参考。1. 从单一侧边栏到多侧边栏先搞懂VuePress的匹配机制1.1 数组式sidebar的局限性VuePress 默认主题里themeConfig.sidebar最常见、也好理解的写法就是数组。数组里每一项可以是一个字符串链接也可以是一对二元组module.exports { themeConfig: { sidebar: [ /, /guide/install, /guide/start, /api/auth, /api/user ] } }这种配置方式的含义很直白所有页面不管你在哪个目录下侧边栏都长这样。对于单模块的博客或者几十个页面的小型文档站它完全够用还能省去很多路径匹配的心智负担。但在多模块场景下它就是灾难——三个模块的文档被强行塞进同一份导航读者点开 API 页面还要在一堆使用指南的链接里翻找侧边栏的“目录引导”作用直接失效。更现实的问题是数组式配置在页面多了以后很难维护。站在全局视角看你很难想象一次把所有链接平铺出来是什么体验列表越来越长、分组全靠视觉空行、新增页面时总会纠结该插到哪个位置。我后来比较深刻的感受是数组式 sidebar 适合当“默认值”用但如果整个站点都是这个单一的默认值别怪 VuePress 不给力其实是信息架构已经超出它的舒适区了。1.2 对象式sidebar与最长路径前缀匹配VuePress 的 sidebar 真正支持多套导航的形态是对象。对象的每一个 key 都是路径前缀value 则是对应路径下显示的侧边栏数组。下面是一个典型的例子module.exports { themeConfig: { sidebar: { /guide/: [install, start], /api/: [auth, user], /blog/: [first-post, second-post] } } }当你访问/guide/install.html时VuePress 会把当前页面的路径去和对象里的 key 做前缀匹配找出能够匹配上的最长路径然后渲染对应的侧边栏。为什么强调“最长匹配”因为对象里可能出现嵌套的 key比如既有/这个兜底又有/guide/在访问/guide/start.html时/guide/比/匹配得更具体侧边栏应该优先取/guide/对应的内容。key 的写法还有一层区别末尾带/的 key 表示匹配整个目录末尾不带/的 key 则视为精确页面路径。拿/guide和/guide/举例前者匹配的是/guide.html这个页面后者匹配/guide/这个目录下的所有页面。很多人第一次配置时会写上不带斜杠的/guide然后发现访问/guide/目录下的页面时侧边栏根本不出现就是因为二者含义完全不同。另外要注意当 sidebar 是对象且找不到任何匹配 key 时页面不会显示侧边栏而不是回退到某个默认导航——除非你显式配置了一个/的 key 作为兜底。这个行为和数组式“所有页面都有同一份侧边栏”形成了鲜明对比也是后面第 5 章要展开的坑之一。2. 三种常用多侧边栏组织方式与实际配置2.1 按目录分组的对象式配置最自然、最普适的多侧边栏方案就是让 sidebar 对象里的 key 对应文档站的顶层目录。以我重构的文档站为例站点被拆成guide、api、blog三个模块每个模块各有独立的导航结构配置如下module.exports { themeConfig: { sidebar: { /guide/: [ { title: 快速上手, collapsable: false, children: [install, start] }, { title: 进阶配置, children: [config, theme, plugin] } ], /api/: [ { title: 认证接口, children: [auth, token] }, { title: 用户接口, children: [user/list, user/detail, user/update] } ], /blog/: [ { title: 2024 归档, children: [posts/vuepress-sidebar, posts/node-script] } ] } } }这套配置的妙处在于读者从顶部导航点击“指南”进入/guide/开头的页面侧边栏自动切换成“快速上手 进阶配置”点击“API 参考”进入/api/开头的页面侧边栏又自动切换成认证和用户接口。导航栏与侧边栏形成了模块联动本质上是因为顶部导航的链接路径和 sidebar key 用的是同一套前缀规则。这一段配置里我用到了collapsable: false它的作用是让“快速上手”这个分组始终保持展开状态不会被读者收成一个标题栏。对分组比较多的模块我建议还是保留默认的可折叠行为不然侧边栏过长时反而影响浏览。我个人的实践是配置时会把每个模块的 key 后面对应的数组尽量保持精简每个分组的 children 控制在五到八项以内。如果太多说明这个模块的页面组织还需要继续拆分而不是硬塞进一个分组里。2.2 页面级key单独页面使用不同侧边栏目录级 key 处理的是“整个目录统一一个侧边栏”的需求可实际项目中总有那么几个特殊页面比如数据概览页、下载中心页、404 提示页它们所在目录的侧边栏不一定适合自己。这时候就可以用页面级 key 来覆盖。看一个混合配置module.exports { themeConfig: { sidebar: { /guide/: [ { title: 快速上手, children: [install, start] } ], /guide/special-install.html: [ { title: 特殊安装方式, children: [windows, macos, linux] } ] } } }这里/guide/special-install.html就是精确页面路径 key末尾不带斜杠。当读者访问/guide/special-install.html时VuePress 匹配到更长、更精确的 key从而显示“特殊安装方式”这一套侧边栏而访问/guide/install.html等其他页面时依然走/guide/的侧边栏。这种写法需要注意一个细节精确页面 key 的 value 虽然是数组但它内部的字符串路径解析规则和目录级 key 并不同。目录级 key 下的install会被拼成/guide/install而页面级 key 下的相对路径会基于该页面所在目录解析。为了避免踩坑我在精确页面 key 的 children 里一律使用完整路径比如/guide/special-install/windows而不是只写windows这样写虽然多敲几个字符但阅读和排错都省心很多。2.3 frontmatter的sidebar字段关闭、自动与指定除了全局配置每个 Markdown 文件的 frontmatter 还能单独控制侧边栏这是多侧边栏体系里很实用的微调手段。--- sidebar: false ---设置为false表示当前页面不显示侧边栏。比如一个独立着陆页、下载引导页或者需要全宽展示的页面都可以用它把侧边栏关掉。--- sidebar: auto ---设置为auto则是让 VuePress 根据页面内的标题层级自动生成侧边栏。这个能力在单篇文章详情页很顺手——它不再依赖全局目录分组而是直接展示当前页面的二级、三级标题导航读者可以快速定位文章结构。不过要注意auto只会根据当前页面内部的标题生成不会包含兄弟页面链接所以它适合文章型页面不适合需要横向导航的模块首页。你还可以在 frontmatter 里直接指定一个完整的侧边栏数组或者对象覆盖全局配置。不过据我所知实际项目里这么用的情况很少因为一旦把侧边栏内容散落到各个页面整个站点的导航结构就失去了集中维护的入口反而难以把控。我更推荐把它当成“异常处理开关”平时绝大多数页面还是由第 2.1 节的全局对象式配置统一定义。3. 多侧边栏配置中的目录结构设计与路径规则3.1 顶部导航模块与侧边栏一一对应的目录划分多侧边栏配置能不能跑得顺一半在 sidebar 配置本身另一半在docs目录的组织方式。我见过不少开发者把多个模块的文件混在一个平铺目录里侧边栏配置再想办法用一堆精确 key 去逐个覆盖最后配置长得像迷宫新增页面时根本不知道应该动哪个 key。正确做法是让目录结构直接映射侧边栏的分组关系。一个顶部导航模块对应一个一级目录侧边栏 key 就指向这个一级目录。比如这样的结构docs/ ├── README.md ├── guide/ │ ├── README.md │ ├── install.md │ ├── start.md │ └── config.md ├── api/ │ ├── README.md │ ├── auth.md │ ├── token.md │ └── user/ │ ├── list.md │ ├── detail.md │ └── update.md └── blog/ ├── README.md └── posts/ ├── vuepress-sidebar.md └── node-script.md在这个结构下/guide/的侧边栏数组只管guide目录下的文件/api/只管api目录。每个模块的导航是内聚的新增页面只需往对应目录里丢一个 Markdown 文件然后再到映射的 sidebar key 下补一行链接。维护成本从“全局找位置”降为“模块内找位置”压力小很多。给一个具体的判断标准如果某一天你需要写一个不属于任何现有模块的页面那正确动作不是给它加一个精确 key而是先考虑新增一个顶层目录并同步在导航栏加上对应入口。目录结构跟导航、侧边栏三者始终保持一致长期维护才不会乱。3.2 children相对路径的拼接规则与完整路径写法VuePress 官方文档对于 sidebar 里 children 字符串的写法其实有约定但新手很容易搞混。当 key 是目录风格比如/guide/时children 里的字符串会被视为相对于该目录的路径。下面是几种等价写法。sidebar: { /guide/: [ { title: 快速上手, children: [install, start] } ] }这里的install等价于/guide/install在构建时寻址到docs/guide/install.md。如果你想写完整路径也完全没有问题children: [/guide/install, /guide/start]两种写法渲染结果一样区别只在于可读性和维护方式。我个人的习惯是如果 children 内部比较简短且所属分组和目录结构一一对应就用相对写法如果 children 涉及跨目录引用或者想一眼看清链接到底指向哪个文件就用完整路径。另外路径里不要写.md后缀也不要写.html后缀。VuePress 内部会处理这种映射关系写了反而容易引发不必要的匹配问题。路径开头的/也尽量保留它表示从docs根目录开始寻址少一个斜杠很容易被解析成相对路径导致 404 或者侧边栏高亮错乱。3.3 分组标题、collapsable与sidebarDepth的配合多侧边栏配置里分组结构通常不止一层这时候分组标题就承担了“二级目录”的角色。我的侧边栏配置里顶层的title对应模块内的章节children 对应章节下的具体页面。如果某个章节下页面很多还可以再往前推进一层出现“分组套分组”的结构/api/: [ { title: 用户接口, collapsable: true, children: [ { title: 基础信息, children: [user/list, user/detail] }, { title: 扩展信息, children: [user/profile, user/settings] } ] } ]这种嵌套分组渲染后会在侧边栏里展示出二级缩进用户能明显感知到“用户接口”这个大章节下还有两个子分类。使用嵌套时需要特别小心外层分组里的collapsable: true控制的是整个大章节能否折叠内层分组的折叠状态则各自独立。如果内层分组没有设置collapsableVuePress 会默认允许折叠。sidebarDepth 则是控制侧边栏里是否显示页面内标题的配置。它默认是 1表示侧边栏的每个链接下额外显示该页面的二级标题h2。如果 sidebarDepth 设为 0侧边栏只显示页面链接本身设为 2则会在 h2 的基础上继续列出 h3。多侧边栏模式下我通常把 sidebarDepth 保持默认 1因为模块文档的导航粒度到“页面内章节”就够了继续深挖会干扰模块间导航的辨识度。4. 自动生成多个侧边栏的Node脚本与维护取舍4.1 手动维护数组的痛点模块一多文件数量上去了手动维护 sidebar 数组会逐渐变得吃力。最典型的问题有三个。第一个是遗忘。新增了一个docs/guide/advanced/ssr.md文件但 sidebar 数组里没有对应条目读者通过上一页的链接跳进去后侧边栏高亮和上下页导航可能乱七八糟。第二个是排序。人类维护长数组时很难保证每个新文件都插到正确位置经常出现“API 接口”分组里混着几个“快速上手”的链接。第三个是重构成本。当某个模块的页面被重命名、移动目录时sidebar 数组里所有相关路径都要同步修改漏掉一个就可能产生断链。我自己维护过近七十个页面的文档站之后对“纯手工维护导航”这种事变得非常警惕。导航配置本质上是一份从目录结构到页面链接的映射既然目录结构在变那这份映射就应该尽量自动化生成而不是靠记忆去保持同步。4.2 遍历目录生成侧边栏配置的脚本思路自动生成多个侧边栏的思路核心就是“遍历目录 → 生成配置对象”。我在项目中写过一个 Node 脚本通过读取文件系统来构造 sidebar 对象这里提供一个思路参考const fs require(fs) const path require(path) const docsRoot path.join(__dirname, ../docs) const topDirs [guide, api, blog] function buildChildren(dir) { const fullDir path.join(docsRoot, dir) const entries fs.readdirSync(fullDir).sort() const children [] for (const name of entries) { const fullPath path.join(fullDir, name) const stat fs.statSync(fullPath) if (name README.md) continue if (name.startsWith(.)) continue if (stat.isDirectory()) { const nested buildChildren(path.join(dir, name)) children.push({ title: name, collapsable: true, children: nested }) } else if (name.endsWith(.md)) { const link /${dir}/${name.replace(/\.md$/, )} children.push(link) } } return children } const sidebar {} topDirs.forEach((dir) { sidebar[/${dir}/] buildChildren(dir) }) console.log(JSON.stringify(sidebar, null, 2))这个脚本的工作流程并不复杂先规定好需要生成侧边栏的顶层目录然后递归遍历这些目录README.md作为模块首页被跳过避免在侧边栏里重复出现普通 Markdown 文件转换为不带后缀的链接路径子目录则递归生成嵌套分组。运行后得到的sidebar对象可以直接贴进 config.js也可以单独放在一个sidebar.js文件里导出config 引进来。脚本里用到的sort()能保证文件按名称排序对于大多数文档站足够用了如果你想按创建时间或者自定义优先级排序可以改成读取文件元数据或者用一个映射表来调整顺序。4.3 脚本生成后的手工补充策略自动生成脚本能解决“基础导航不丢失”的问题但它生成的侧边栏通常是扁平的列表缺少人工精心设计的分组标题、顺序和collapsable设置。比如某个模块想强调“快速上手”在安装指南之前展示脚本按文件名排序是做不到这一点的。所以我在实际项目中采用“自动生成 手工覆盖”的混合策略脚本负责生成所有页面链接的完整集合然后我在关键模块中手动重写这几个分组的顺序、标题和嵌套关系。比如在guide模块里我会手工指定“快速上手”分组排第一并且把collapsable: false写死其余分组保持默认的自动折叠行为。这个过程不能全自动但至少不用手写七十个链接心智负担少了很多。还有一个小技巧脚本生成后我通常会把sidebar对象放到单独一个themeConfig.sidebar变量里再抽时间检查一下生成的链接和实际目录是否一致。检查方式很简单——在 VuePress dev 模式跑起来后随机点开几个深层页面看侧边栏是否存在、高亮是否正确。自动生成只是降低维护成本的第一步人工评审依然是最后一道防线。5. 多侧边栏落地时的坑与排查记录5.1 目录key结尾斜杠最容易被忽略的细节我第一次用对象式 sidebar 时配置里写的是/api而不是/api/结果访问/api/auth.html时侧边栏一直不出现。一开始还以为是路径写错了排查了半天才发现问题出在 key 结尾的斜杠上。前面提过末尾带/的 key 才表示“匹配这个目录下的所有页面”不带/的 key 会被当成“匹配一个精确页面路径”。/api匹配的是/api.html这个具体页面而我的文档站里根本没有docs/api.md只有docs/api/目录下的一堆文件所以匹配始终落空。改成/api/后所有api目录下的页面都能正确命中侧边栏。这个细节排查起来有点隐蔽因为 VuePress 不会报错只会静默地不渲染侧边栏。我的经验是如果你发现某个目录的侧边栏没生效先去检查 sidebar 对象里对应的 key 结尾是不是有/如果多个 key 嵌套再看最长匹配优先级是否被更长的 key 抢走了。5.2 无匹配侧边栏时的回退逻辑如果 sidebar 被配置成对象某个路径下的页面没有匹配到任何 key侧边栏不会回退到某个默认数组而是直接不显示。这是一种很微妙的差别很多从数组式迁移过来的同学会忽略。我遇到过一种典型场景站点同时有/guide/和/api/模块但根目录README.md也就是/首页没有配置任何 sidebar key结果首页侧边栏消失了。首页没有侧边栏其实问题不大但如果站点还有/about/、/faq/这类非核心模块它们的页面都没有侧边栏用户进入后想跳到其他模块就只能靠顶部导航体验就打折扣。解决方案有两种。一种是给这些模块也加上对应的 sidebar key哪怕 children 很短另一种是额外配置一个/的 key 作为兜底侧边栏让所有未匹配页面至少有一份全局导航。两种方案并不冲突我会同时用核心模块走各自精确的 sidebar非核心页面走/兜底这样既保证模块独立性也保证页面不会空空荡荡。5.3 多语言站点中的多侧边栏结构多侧边栏在 VuePress 多语言站点里会再多绕一层。如果站点有多语言themeConfig.sidebar应该被放进每个 locale 配置里而不是写在顶层。我举个简化的例子themeConfig: { locales: { /zh/: { sidebar: { /zh/guide/: [install, start], /zh/api/: [auth, user] } }, /en/: { sidebar: { /en/guide/: [install, start], /en/api/: [auth, user] } } } }每个语言的 sidebar 对象里的 key 需要带上语言前缀否则匹配又会落空。这一点和目录结构的组织方式有联动关系多语言站点的docs目录通常先按语言分一层再按模块分一层实际的文件路径可能是docs/zh/guide/install.md对应的访问路径是/zh/guide/install.html那 sidebar key 自然也要写成/zh/guide/。多语言场景下还有一个容易踩的坑是语言包切换。读者在中文站点访问/zh/api/的侧边栏是/zh/api/那套切换到英文站点后路径变成/en/api/侧边栏又需要对应显示英文导航。如果两个语言有很多共享内容可以考虑用脚本同时为所有语言生成 sidebar然后脚本里按语言前缀做一次映射避免在多个语言配置文件里反复手写重复结构。最后分享一个实际体会多侧边栏的配置方案并不复杂真正的成本在文件组织和长期维护上。我强烈建议在一开始设计文档目录时就把“顶部导航模块 一级目录 sidebar key”这条对应关系定死后续无论手动配置还是脚本生成都会顺很多。如果你正在迁移一个已经摊成大饼的文档站不妨先花一个下午把目录结构调整到位再回来改 sidebar 配置你会发现原本一堆花式 key 的写法最后都会被清洗成清晰的三五个目录级 key。