Skip to content

项目结构、文件位置与 URL 规则 ​

这一篇解决最基础也最容易出错的问题:文件到底该放哪里,浏览器里的 URL 又是由什么决定的。你不需要先理解 VitePress 的全部原理,但必须分清「我编辑的源文件」和「工具生成的文件」,否则很容易改错位置,或者把构建产物提交进仓库。

本站的 VitePress 源目录是 docs。docs/.vitepress/config.ts 中设置了 rewrites: createRewrites({ srcDir: "docs" })、cleanUrls: true、lastUpdated: true 和 sitemap,主题的 permalink 插件也处于启用状态。实际公开地址以页面 frontmatter 的 permalink 为主,文件路径只是源文件位置,不应靠猜 URL。

先认识源文件和生成文件 ​

类型常见路径是否手改为什么
文章源文件docs/posts/**/*.md可以你的正文和文章 frontmatter,是内容的事实来源
目录页源文件docs/catalog/**/*.md可以提供目录页 frontmatter 和 path 扫描入口
静态资源源文件docs/public/**可以会原样复制到站点根路径,例如 /home/bg18.webp
配置文件docs/.vitepress/config.ts、sidebar.ts、search-manifest.ts、Nav.ts、Features.ts谨慎修改控制路由、侧边栏、搜索清单、导航和首页卡片
构建产物docs/.vitepress/dist/不可以pnpm docs:build 生成,部署也从这里取文件
开发缓存docs/.vitepress/cache/不可以Vite/VitePress 自动维护,删除后也会重建
搜索清单docs/public/search-manifest.json不可以启动时由 search-manifest.ts 扫描 docs/posts 自动写出
包体分析文件stats.html不可以构建分析工具生成,.gitignore 已忽略

生成文件为什么会出现在源码目录里?因为工具有时会把结果放到公共目录或 .vitepress 旁边。例如 docs/public/search-manifest.json 看起来像普通静态文件,实际上是 plugins.ts 在加载时调用 getSearchManifest() 并 writeFileSync 生成的;它被 .gitignore 忽略,下一次启动又会重写。另一些工具把 Pagefind 索引、RSS、sitemap 放进 docs/.vitepress/dist/。记住一个原则:能被构建命令重新生成的文件,不作为手写源文件修改。

与本指南相关的完整目录树 ​

text
E:\vitepress-teek-my-blog\teek-hyde\
├─ docs\                                  # VitePress 的 srcDir
│  ├─ catalog\                            # 目录页,只负责生成文章目录入口
│  │  ├─ 技术笔记.md                       # /catalog/技术笔记
│  │  ├─ 技术笔记\
│  │  │  └─ 项目记录.md                    # /catalog/技术笔记/项目记录
│  │  ├─ 生活随笔.md                       # /catalog/生活随笔
│  │  └─ 算法竞赛.md                       # /catalog/算法竞赛
│  ├─ posts\                              # 正式文章源文件
│  │  ├─ 技术笔记\
│  │  │  ├─ 项目复盘模板.md                # /posts/技术笔记/项目复盘模板
│  │  │  └─ 项目记录\
│  │  │     └─ .gitkeep                   # 空目录占位,不是文章
│  │  ├─ 生活随笔\
│  │  │  └─ .gitkeep
│  │  └─ 算法竞赛\
│  │     └─ .gitkeep
│  ├─ public\                             # 静态资源根路径 /
│  │  ├─ home\bg18.webp                   # 封面常用图
│  │  ├─ images\posts\<slug>\             # 文章正文图片和自定义封面
│  │  └─ search-manifest.json             # 自动生成,不手改
│  ├─ writing-guide\                      # 写作指南说明页
│  └─ .vitepress\
│     ├─ config.ts                        # 全局路由、SEO、markdown、sitemap 等
│     ├─ sidebar.ts                       # 扫描 posts 生成文章侧边栏
│     ├─ search-manifest.ts               # 扫描 posts 生成搜索清单元数据
│     ├─ plugins.ts                       # Pagefind、RSS、搜索清单写入等
│     ├─ ConfigHyde\Nav.ts                # 手工维护的顶部导航
│     ├─ ConfigHyde\Features.ts           # 手工维护的首页 Banner 功能卡片
│     ├─ dist\                            # 构建产物,禁止手改
│     └─ cache\                           # 开发缓存,禁止手改
├─ scripts\audit-site.mjs                 # pnpm docs:audit 使用的 SEO 审计脚本
├─ .cnb.yml                               # CNB 安装、构建、审计、部署流程
├─ package.json                           # pnpm 脚本定义
└─ stats.html                             # 可能生成的包体分析文件,已忽略

.gitkeep 不是 VitePress 的特殊文章格式,而是 Git 的约定:Git 不跟踪空目录,所以先放一个空文件占位。扫描器只处理 .md,不会把 .gitkeep 当成文章。.vitepress 下以点开头的目录、posts 下以下划线或点开头的条目会被扫描器跳过,因此不要用 _draft 之类的名字指望它自动变成公开文章。

docs/catalog 与 docs/posts 的分工 ​

项目docs/catalog/docs/posts/
角色目录入口页文章正文页
典型文件docs/catalog/技术笔记.mddocs/posts/技术笔记/项目复盘模板.md
页面靠什么生成layout: page + catalogue: true + pathMarkdown 正文和文章 frontmatter
是否通常有正文不需要,通常只保留 frontmatter需要 H1 和正文
是否进入目录扫描不被 search-manifest.ts 扫描被侧边栏、搜索清单和文章系统扫描
是否进入首页/分类/标签/归档不进入article: true 时进入
是否显示评论固定关闭由 comment: true 开启
是否参与 Pagefind固定关闭由 pagefind-indexed: true 开启

目录页的 path 指向 docs/posts/ 下的真实文章目录,例如:

yaml
path: posts/技术笔记/项目记录

这个值不带开头的 /,也不包含 docs/。它相当于从 docs/ 开始的相对路径。目录页的 permalink 则通常镜像为:

yaml
permalink: /catalog/技术笔记/项目记录

文章目录中为什么不能放 index.md?原因很具体:sidebar.ts 会跳过 name.toLowerCase() === "index.md" 的文件,search-manifest.ts 也会过滤掉 index.md。即使你写了一个 index.md,它也可能不出现在侧边栏和自定义搜索清单里,还会和目录路由产生歧义。正确做法是给每篇文章一个有意义的文件名,目录入口放到 docs/catalog/ 下。

本站启用了 cleanUrls: true,所以页面在浏览器里不显示 .html。你写站内链接时也不要加 .html。例如应该写 /writing-guide/new-article,不要写 /writing-guide/new-article.html。

内容源文件示例推荐 permalink最终访问地址
一级目录页docs/catalog/技术笔记.md/catalog/技术笔记/catalog/技术笔记
二级目录页docs/catalog/技术笔记/项目记录.md/catalog/技术笔记/项目记录/catalog/技术笔记/项目记录
一级文章docs/posts/技术笔记/01.git-basics.md/posts/技术笔记/git-basics/posts/技术笔记/git-basics
二级文章docs/posts/技术笔记/项目记录/01.vitepress-deploy-guide.md/posts/技术笔记/项目记录/vitepress-deploy-guide/posts/技术笔记/项目记录/vitepress-deploy-guide

中文 URL 在地址栏中可能显示为百分号编码,例如 /catalog/%E6%8A%80%E6%9C%AF%E7%AC%94%E8%AE%B0,这是正常现象。Markdown 链接和 frontmatter 中直接写中文即可,例如:

md
[技术笔记目录](/catalog/技术笔记)

显式 frontmatter 建议始终包含:

yaml
---
title: VitePress 部署指南
description: 从构建到 CNB 部署的可复现流程。
permalink: /posts/技术笔记/项目记录/vitepress-deploy-guide
categories:
  - 技术笔记
tags:
  - VitePress
  - 部署
article: true
sidebar: true
pagefind-indexed: true
comment: true
---

当前虽然启用了 autoFrontmatter: true,但配置里的 exclude: { title: true, date: true } 会让插件跳过已有普通 frontmatter 的文章;因此不要依赖它给文章补 permalink、分类或封面。date 由单独的 article-date.ts 初始化器在文件首次 Git commit 后,于启动开发服务器或构建时自动写入;读取的是第一次 commit 的作者时间,不是 push 或构建时间,不需要手填。省略 permalink 时,路由可能按 VitePress 源文件路径推导,但这不是稳定、可读的公开 URL,仍必须显式写 /posts/...。

所以流程必须是:先写显式 permalink,再启动开发服务器。 不要先空着看看页面能不能打开。 另外,sidebar.ts 和 search-manifest.ts 在缺少 permalink 时各自有一个回退逻辑:大致按 /posts/<一级分类>/<去掉编号的文件名> 生成链接。这个回退不会可靠地保留多层子目录,所以它只能算兜底,不能作为正式规则。尤其是 项目记录 这类二级目录文章,更必须在 frontmatter 中显式写完整 permalink。 permalink 还应当满足:

  • 以 /catalog/ 或 /posts/ 开头,与内容类型一致;
  • 全站唯一,不与已有文章、目录页或功能页重复;
  • 发布后保持稳定;如果必须改,要同步更新所有站内链接,并在托管平台做旧地址重定向;
  • 不包含 .html、空格、反斜杠或结尾斜杠;统一使用 /;
  • 文件路径可以调整,但公开 permalink 不要无故跟着改。

rewrites 和 permalink 插件的存在意味着:源文件相对路径、VitePress 内部路由和最终公开 URL 不是一回事。你可以把「源文件路径」理解为仓库里的存放位置,把 permalink 理解为公开承诺。新增、移动或重命名文件后,不要凭文件名猜浏览器地址,打开页面以 frontmatter 的 permalink 为准。

文件和目录命名规则 ​

对象推荐写法说明
一级分类目录docs/posts/技术笔记/、docs/posts/生活随笔/目录名就是分类的物理来源,categories 应与其保持一致
文章子目录docs/posts/技术笔记/项目记录/与 docs/catalog/技术笔记/项目记录.md 镜像;不要在 posts 放 index.md
文章文件名01.vitepress-deploy-guide.md两位编号 + 点 + 英文 slug + .md
文章 H1 / titleVitePress 部署指南可以是中文,且 H1 与 title 保持一致
图片目录docs/public/images/posts/vitepress-deploy-guide/与文章 slug 对应,便于查找和迁移
正文图片引用/images/posts/vitepress-deploy-guide/build-success.webp对应 docs/public 下的资源,不要写 docs/public/...

命名的重要约束:

  • 文件名和目录名不要包含空格,不要包含 /、\、:、*、?、<、>、| 等路径或 Windows 非法字符。
  • 文章文件名推荐 ASCII slug,避免大小写混用;URL 使用小写和连字符更稳定。文件和目录名可以用中文,但要保持全站一致,不要今天写 技术笔记、明天写 技术笔记 (末尾空格)。
  • NN. 前缀用于排序。sidebar.ts 和 search-manifest.ts 都会读取 ^(\d+)\.,并在部分回退链接中去掉前缀。它不决定首页时间排序。
  • 不要在 posts 目录中使用 index.md;不要用 . 或 _ 开头的条目来「临时发布」,这些条目会被扫描器跳过。
  • 发布后不要为了整理好看而随意改文件名。即使显式 permalink 保住了 URL,文件重命名还是会影响排序、图片目录、Git 历史和后续维护。

如果你确实要重命名或移动,优先使用 git mv 保留历史,并把它当作一次需要重新验收的结构变更,而不是普通保存。

文章图片和封面路径 ​

正文图片的真实文件放在:

text
docs/public/images/posts/vitepress-deploy-guide/build-success.webp

在 Markdown 中引用时省略 docs/public,因为 docs/public 的内容会映射到站点根路径:

md
![VitePress 构建成功后的终端输出](/images/posts/vitepress-deploy-guide/build-success.webp)

规则说明:

  • 图片必须放在 docs/public/ 下,且引用路径以 / 开头;写成相对路径或写成 /public/... 都会导致 404。
  • 每张正文图片都必须有 alt。审计只检查 alt 属性是否存在,空 alt 也可能通过,所以更重要的是写出能描述图片内容的实质文字,帮助屏幕阅读器;不要写没有信息量的占位文字。
  • 推荐使用 .webp 等浏览器友好的格式;上传原图前先压缩,避免首页和文章加载过慢。
  • coverImg 也使用站点根路径。需要固定首页卡片或固定分享图时,可写 /images/posts/<slug>/cover.webp 或仓库已有 /home/bgNN.webp。
  • 不写 coverImg 时,首页文章卡片会先随机使用正文中可识别的图片;正文也没有图片时,从 /home/bg*.webp 随机选择。只有希望封面固定、或希望社交分享图稳定时才必须写 coverImg。
  • 首页随机封面不会改变 SEO 分享图;不写 coverImg 时,OG/Twitter 分享图仍然使用站点默认图。正文图片请使用 /images/... 这种站内绝对路径,相对路径不会稳定进入候选列表。

排序:文件名编号、sidebarSort、date、sticky 和 top 的区别 ​

这些排序信号作用在不同页面,不能互相替代。理解它们的最小记忆是:侧边栏看编号或 sidebarSort,首页和时间线看 date,置顶看 sticky,精选卡片看 top。

机制写在哪里主要影响规则和注意点
文件名数字前缀文章文件或子目录名侧边栏、搜索清单的目录/文件顺序形如 01.slug.md 或 10.子目录;sidebar.ts 和 search-manifest.ts 读取以数字加点开头的编号并升序排列。它不决定首页按日期展示的顺序。
sidebarSort文章 frontmatter,正整数文章在侧边栏和搜索清单中的顺序优先级高于文章文件的数字前缀;数字越小越靠前。只对文章排序,不会给整个一级分类排序。
date文章 frontmatter首页文章流、分类、标签、归档的时间顺序主题按日期从新到旧排序。统一写 YYYY-MM-DD HH:mm:ss;不要用它控制侧边栏。
sticky文章 frontmatter首页和按日期排序的文章流中置顶非零值会排在同组普通文章前面;多个 sticky 按数值升序,再按日期排序。它经常和 date 配合使用,但不是侧边栏排序。
top文章 frontmatter,通常写 top: 1首页精选文章卡片只要值为真就会进入精选候选,最终显示数量由主题 topArticle.limit 控制。它不等于 sticky,也不改变侧边栏。
目录名数字前缀docs/posts/ 下的子目录二级目录的侧边栏/搜索清单顺序例如 10.工具。一级分类的先后由两份 categoryOrder 决定,不由目录名单独决定。

一个常见误区是:给文章加上 date 就以为左侧栏会按日期排。实际侧边栏由 sidebar.ts 读取文件名编号或 sidebarSort;日期只用于主题的文章流和归档。反过来,只改文件名编号也不会改变首页文章顺序。

如果目录名使用了数字前缀,例如 docs/posts/技术笔记/10.工具/,目录页的 path 必须写成目录的真实名称:

yaml
path: posts/技术笔记/10.工具

不要写成 posts/技术笔记/工具,除非文件系统里的目录确实叫「工具」。新手如果不想同时维护编号和真实路径,建议目录名保持无编号的稳定名称,只用文章文件名的 NN. 前缀控制文章顺序。

新增、修改、删除时应该改哪里 ​

你的操作必须改的位置通常还要检查
在已有目录新增文章docs/posts/<已有目录>/NN.slug.md图片目录、permalink、首页/搜索/分类/标签/归档
新增二级目录docs/posts/<父目录>/<新子目录>/ 和 docs/catalog/<父目录>/<新子目录>.md目录页 path、Nav 子菜单(可选)、Features(可选)
新增一级分类新的 docs/posts/<分类>/、docs/catalog/<分类>.md、Nav.ts、两份 categoryOrderFeatures.ts(需要首页卡片时)、第一篇文章、所有链接
修改文章标题或描述文章 frontmatter 和同页 H1(标题变化时)首页卡片、侧边栏文字、SEO 描述、搜索
修改文章分类或标签文章 frontmatter,必要时移动文件/categories、/tags、对应目录页、旧链接
修改文章 permalink文章 frontmatter、所有站内引用托管平台旧地址重定向、sitemap、RSS、搜索
重命名或移动文件文件路径;如果公开 URL 不变则保留原 permalink图片相对引用、侧边栏顺序、Git 历史
新增图片docs/public/images/posts/<slug>/ 和文章引用alt、路径大小写、构建后的图片是否存在
删除文章文章文件、图片、所有手工链接目录页、侧边栏、首页、分类、标签、归档、搜索、RSS、旧 URL
删除一级分类posts 目录、catalog 目录页、Nav.ts、两份 categoryOrder、Features.ts所有文章 permalink、旧目录 URL、重定向
修改分类顺序sidebar.ts 和 search-manifest.ts 的 categoryOrderNav 顺序是否也要同步、重启开发服务器

这里的「通常还要检查」不是都要手工修改。侧边栏、目录列表、首页、分类、标签和归档大多会自动重新扫描;真正需要手工维护的是 Nav.ts、Features.ts、两份 categoryOrder 以及其他 Markdown 中手写的链接。下一篇内容、数据与页面联动关系会把自动和手动部分逐项拆开。

常见结构错误与后果 ​

错误写法会发生什么正确做法
docs/posts/技术笔记/index.md侧边栏和搜索清单主动跳过;可能和目录路由冲突改成 01.article-slug.md,目录入口放 docs/catalog/
文章没有 permalink可能按源文件路径生成路由,不能得到稳定可读的公开地址在首次启动开发服务器前显式写 /posts/...
两个页面写同一个 permalink构建或路由冲突,至少一个页面无法按预期访问全站搜索旧值,改成唯一地址
写给浏览器看的地址带 .htmlcleanUrls: true 下链接可能与实际路由不一致站内链接统一省略 .html
目录页 path: /posts/技术笔记当前 path.join 通常仍能解析,但与团队约定的相对路径写法不一致,跨环境时不易排查统一写成 path: posts/技术笔记
目录页 path 指向不存在的目录目录页打开后看不到文章先在 docs/posts/ 创建真实目录,再让 path 精确匹配
正文图片放 docs/posts/...图片不会作为公共静态资源复制到 /images/...放到 docs/public/images/posts/<slug>/
图片引用写成 /public/images/...docs/public 已映射为根路径,多写 /public 会 404写成 /images/posts/<slug>/<file>.webp
修改了 docs/.vitepress/dist/ 里的 HTML下一次构建被覆盖,改动丢失改对应的 docs/posts 或 docs/catalog 源文件
新增一级分类只建了文件夹顶部导航、分组顺序或首页卡片不会自动出现按目录页流程同步 Nav、两份 categoryOrder 和可选 Features
发布后改了 permalink 却没做重定向旧链接和外部收藏全部失效尽量不改;必须改时更新链接并配置托管平台重定向
在 posts 下手改 search-manifest.json下次启动被 search-manifest.ts 重新生成覆盖改 frontmatter 或 search-manifest.ts 源码

结构检查清单 ​

新增或调整结构后,按顺序检查:

  • 文章只在 docs/posts/,目录页只在 docs/catalog/,没有在 posts 中使用 index.md。
  • 公开地址来自显式且唯一的 permalink,不是随机 /pages/...。
  • 文章 URL 以 /posts/ 开头,目录页 URL 以 /catalog/ 开头。
  • 所有站内链接都使用 /path,没有 .html、反斜杠或多余空格。
  • 文章的 H1 与 title 一致,并且整页只有一个 H1。
  • 一级目录与 categories 对应;二级目录不误写成新分类。
  • 正文图片在 docs/public/images/posts/<slug>/,引用以 /images/... 开头且每张有 alt。
  • 如果写了 coverImg,它是明确存在的路径;如果故意省略,已确认首页随机封面和分享图行为符合预期。
  • 侧边栏排序意图明确:文章用 NN. 或 sidebarSort,目录顺序有需要时使用数字前缀或 categoryOrder。
  • 首页时间排序使用合法 date,置顶使用 sticky,精选卡片使用 top,没有混用。
  • 新增一级分类时,Nav.ts、sidebar.ts 和 search-manifest.ts 已同步,Features 按需更新。
  • 没有修改 dist、cache、search-manifest.json 或 stats.html。
  • 开发环境、生产构建、SEO 审计和预览都通过,再提交并推送。

结构正确后,下一关是理解字段会传播到哪些页面。请继续阅读内容、数据与页面联动关系。

最后更新于: