docsify 实战指南:零构建文档站点的原理与应用
2026/9/18 22:07:52 网站建设 项目流程

docsify 这个工具我用了很长时间,从最早给团队搭内部组件库文档,到后来做开源小项目的说明站点,几乎每次都选它。原因很简单:它是我见过的“零成本”文档方案里最舒服的一个,不需要花时间配置构建流程,也不需要守着控制台等编译结果。

但我也发现很多朋友第一次用它时会有点懵——因为 docsify 的工作方式和常规静态站点生成器完全不一样,上来就写错了思路。这篇把我这几年实际使用的核心经验整理一遍,从内部原理到多文档管理,再到常见的坑和排查思路,尽量一次说清楚。

1. 内容整体设计与思路拆解

1.1 它到底和 VuePress/GitBook 有什么不一样

先说明一个最容易误解的点。很多人一听“文档站点生成器”,就默认它和 VuePress、GitBook 是一类东西——都是把你写的 Markdown 文件“编译”成静态 HTML,生成一个完整的站点目录,然后上传服务器。

docsify 不是这个路子。它没有编译环节,甚至严格来说没有“生成静态站点”这一步。它的核心做法是:在浏览器里动态加载 Markdown 文件,然后实时解析成 HTML 渲染到页面上。

打个比方:VuePress 类似“先做好一桌菜再端上来”,所有页面提前生产好,用户访问的时候直接吃成品。docsify 更像“点菜后现炒”,用户在浏览器里打开页面时,docsify 才去服务器拉取对应的.md文件,现场解析、现场渲染。

这意味着几个重要结果:

  • 部署就是复制文件夹,没有build这一步,写完 Markdown 刷新浏览器就能看效果。
  • 首次加载会比静态 HTML 慢一点,因为要先加载框架和解析文档。
  • 对搜索引擎的友好度天然要差一些,后续需要额外做优化。

这个设计思路的取舍非常清晰:docsify 追求的是“写文档”和“看文档”之间零摩擦。对于内部工具、个人笔记、项目说明这类场景,它省掉的构建成本远超它牺牲的那点性能。

1.2 为什么“零构建”是很多团队的刚需

我在团队里推 docsify 之前,很多人提过一个反对意见:我们已经有 VuePress 了,为什么还要换?

我的理由很简单:不是所有人都需要或应该维护一个构建环境。

团队里的文档维护者往往是前端工程师,但也可能是后端、算法、测试甚至产品经理。要求每个人都懂 Node 构建流程,理解 npm 依赖、构建报错、路由配置,这件事本身就把“写文档”的门槛抬高了。

docsify 让文档回归到了“写 Markdown、推 Git”的本质。对维护者来说,工作流简化成了:

git clone 项目 写 docs/my-page.md git add . git commit -m "docs: 新增xxx说明" git push

不需要本地构建验证,不需要关心编译缓存,不需要处理构建时报错。写文档变成了一件足够“轻”的事情,这恰恰是它最大的价值。

而且对于不需要复杂定制、不需要 SEO 依赖的文档站,零构建方案带来的维护成本下降是立竿见影的。每次发布只需更新源文件,不需要重新生成一遍全量 HTML,多环境部署时也少了很多麻烦。

1.3 什么场景适合用它

根据我的实际使用体验,这几个场景和 docsify 的匹配度很高:

  • 企业内部工具/组件库文档:访问者是内部员工,对加载速度和 SEO 没硬性要求,但文档更新频率高,需要“改完立刻生效”。
  • 个人知识库 / 学习笔记:想随手记点什么,一个仓库 + docsify,加个域名就能看,数据完全自持。
  • 开源项目的 README 增强站点:GitHub 仓库自带的 README 太拥挤,用 docsify 低成本搭一个简洁的说明站点,不需要额外维护构建脚本。
  • 课程讲义 / 线下分享材料:资料无需公开,放在内网或者轻量服务器上即可,随时更新。

如果你的需求是公开文档站、对 SEO 有强依赖、或者页面数量几千级且希望秒开,那 VuePress、Docusaurus、Astro 这类静态站生成器是更合适的选择。docsify 适合的是“轻快灵”的文档场景,不必勉强。

2. 核心细节解析与实操要点

2.1 快速开始:三个命令跑起来

先把最基础的跑通流程走一遍,再深入解释每个文件的作用。

假设你已经安装了 Node.js,直接用官方推荐的方式初始化:

npm i -g docsify-cli docsify init ./docs docsify serve docs

打开 http://localhost:3000 就能看到默认页面。这三行命令做的事情分别对应:安装命令行工具、初始化基础文件、启动本地开发服务器。

其中docsify init会在./docs目录下生成三个核心文件:

docs/ ├── index.html # 站点入口,所有配置在这里 ├── README.md # 默认主页内容,对应访问根路径时显示 └── .nojekyll # 让 GitHub Pages 不忽略下划线开头的目录

很多人以为这就是全部了,其实这只是起点。后续所有的功能扩展都是围绕这几个文件做的。

2.2 index.html 是你的“总控台”

docsify 的所有全局配置都在index.html里,通过window.$docsify对象暴露给运行时。

一个最基础的 index.html 长这样:

<!DOCTYPE html> <html lang="zh-cn"> <head> <meta charset="UTF-8"> <title>我的文档站</title> <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"> <meta name="description" content="使用 docsify 搭建的文档站点"> <meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no"> <link rel="stylesheet" href="//cdn.jsdelivr.net/npm/docsify@4/lib/themes/vue.css"> </head> <body> <div id="app">加载中...</div> <script> window.$docsify = { // 全局配置都写在这里 repo: 'username/repo', maxLevel: 3, loadSidebar: true, subMaxLevel: 2, search: { placeholder: '搜索文档' } } </script> <script src="//cdn.jsdelivr.net/npm/docsify@4/lib/docsify.min.js"></script> <script src="//cdn.jsdelivr.net/npm/docsify@4/lib/plugins/search.min.js"></script> </body> </html>

需要注意一个顺序问题:window.$docsify的配置对象要先定义,然后才加载docsify.min.js。因为 docsify 在加载时会读取这个全局配置对象,顺序反了会导致配置全部失效,这个问题非常容易踩到。

2.3 侧边栏和顶部导航的正确配置方式

docsify 默认情况下没有侧边栏。想启用侧边栏,需要在index.html的配置项里加一行loadSidebar: true,然后创建_sidebar.md文件,在文件里写导航目录。

_sidebar.md 的写法是 Markdown 链接的语法,但有一个很关键的点:链接地址不写.md后缀

<!-- docs/_sidebar.md --> - [首页](/) - [快速开始](/quickstart) - [进阶配置](/config) - [侧边栏](/sidebar) - [导航栏](/navbar) - [封面](/) - [常见问题](/faq)

解释一下为什么链接不能带.md。docsify 的路由机制是:当前 URL 的 hash 部分决定了要加载哪个 Markdown 文件。比如 URL 是http://localhost:3000/#/quickstart,它会自动去加载/quickstart.md。所以你在侧边栏里写[快速开始](quickstart.md)的时候,反而会导致它去找quickstart.md.md这个文件,然后 404。

这个细节几乎每个新用户都会踩一次,我现在写侧边栏时已经养成习惯,所有链接一律不带后缀。

顶部导航栏的配置方式是类似的,创建_navbar.md文件:

<!-- docs/_navbar.md --> - [首页](/) - [指南](/guide) - [GitHub](https://github.com/yourname/yourrepo)

注意,启用导航栏不需要额外配置项,只要检测到_navbar.md文件存在,导航栏就会自动加载。这一点和侧边栏不同,比较隐蔽,容易让人摸不着头脑。

还有一个常用配置项是subMaxLevel。它控制的是一篇 Markdown 文档内的标题层级,哪些会出现在侧边栏里。比如设置subMaxLevel: 2,表示侧边栏会展示当前文档的一级标题(H1)和二级标题(H2)。这个配置不需要在_sidebar.md里手动写子项,它会自动从当前文档的标题结构里解析出来。

我的常用组合是:主页侧边栏用_sidebar.md控制整体站点层级,然后subMaxLevel: 2让每篇长文档的二级标题自动展示,二者搭配效果很干净。

2.4 封面页与主页的差异

docsify 支持封面页(cover page)和主页(README),很多人会把这两个概念搞混。

封面页是网站的落地首页,通常带一个大标题、一句简介和一个按钮,视觉更偏向“品牌展示”。主页对应的是根路径/,实际上是展示文档的正文内容。

两者可以共存。访问站点时,先看到封面页,点击“进入文档”按钮后才进入主页内容。

启用封面的方式很简单,在配置里加上:

window.$docsify = { coverpage: true }

然后创建一个_coverpage.md

# 我的工具文档 > 一个基于 docsify 的轻量文档站点 [快速开始](quickstart) [GitHub](https://github.com/yourname/yourrepo)

配置好之后,初始 URL 会显示封面页,根路径的内容自动移动到下一个页面。这里有个细节:配置了 coverpage 之后,如果想回到主页内容,侧边栏或导航栏里的首页链接应该指向另一个文档(通常是home.md或某个具体页面),而不是/,因为/被封面占用了。如果不希望封面影响正常的首页访问,可以直接把_coverpage.md的内容写成简单的品牌介绍,再在页面里放一个明显的按钮引导进入文档主体。

2.5 一个坑:GitHub Pages 与.nojekyll

我们平时把 docsify 文档托管到 GitHub Pages 时,需要在仓库根目录放一个.nojekyll文件。

原因是 GitHub Pages 默认会用 Jekyll 处理你的站点文件,而 Jekyll 会忽略所有以下划线开头的文件或目录。docsify 的侧边栏文件叫做_sidebar.md,导航栏文件是_navbar.md,封面文件是_coverpage.md,全部都是下划线开头。没有.nojekyll文件的话,这些文件会被 GitHub Pages 忽略掉,然后侧边栏、导航栏全都不显示。

这个问题常见到什么程度?很多新手部署完发现页面能打开,但侧边栏不见了,疯狂排查配置问题,最后发现只是少了这个空文件。

docsify init命令会自动生成.nojekyll,所以如果你是用命令行初始化的,一般不会遇到这个问题。但如果你的项目是手动创建文件结构,很容易漏掉,务必注意。

3. 实操过程与核心环节实现

3.1 自定义导航栏和侧边栏的分级做法

先看一个实际项目里的_sidebar.md结构。这个项目是一个内部数据平台的使用文档,侧边栏按功能模块划分,每个模块下有不同的子页面:

<!-- docs/_sidebar.md --> - [平台简介](/) - 数据接入 - [接入流程](/data-access/flow) - [API 说明](/data-access/api) - [数据格式规范](/data-access/format) - 可视化分析 - [创建报表](/analysis/report) - [图表配置](/analysis/chart) - [筛选器使用](/analysis/filter) - 权限管理 - [角色说明](/permission/roles) - [权限配置示例](/permission/examples) - [常见问题](/faq)

注意第二层级的写法。docsify 的侧边栏列表没有默认的折叠功能,所谓“分组”只是视觉上的缩进和层级展示,并不是手风琴折叠菜单。如果需要可折叠的侧边栏,社区里有现成的插件可以扩展,比如docsify-sidebar-collapse,但这个插件现在更新频率不高,使用前先测试一下和当前 docsify 版本的兼容性。

一个关于侧边栏的经验:不要尝试把所有内容都塞进侧边栏,层级太深阅读体验反而差。我一般控制在三级以内,超过三级的内容就直接写进文档内部的标题层级,让subMaxLevel自动去处理。

3.2 引入全文搜索插件

docsify 官方没有内置搜索功能,需要额外加载一个插件。好在使用方式非常简单,在index.html中引入插件脚本,并在配置项中增加搜索配置:

<script src="//cdn.jsdelivr.net/npm/docsify@4/lib/plugins/search.min.js"></script>
window.$docsify = { search: { placeholder: '搜索文档', noData: '未找到相关结果', depth: 3, paths: 'auto', maxAge: 86400000, // 缓存搜索索引的时间,单位毫秒,一天 namespace: 'my-docs' } }

说明几个参数的用途:

  • placeholder:搜索框的占位提示文字。
  • paths:指定搜索匹配哪些路径。'auto'会搜索所有页面,也可以传一个数组只搜索指定页面,比如['/', '/quickstart', '/faq']
  • maxAge:搜索索引在本地浏览器的缓存时间,单位毫秒。默认一天,如果你的文档更新频繁,可以调小一点,比如60000(一分钟),避免用户缓存了旧的搜索索引。
  • namespace:搜索索引存储时的命名空间。如果同一个域名下部署了多个 docsify 站点,这个参数可以避免索引互相覆盖。

搜索插件的原理是:docsify 在站点加载时遍历所有的 Markdown 文件,在浏览器端生成一个本地索引,搜索时直接在这个索引里匹配。所以它不依赖后端服务,部署非常轻便,但相应的,如果文档量特别大(比如上千页),首次构建索引会有轻微卡顿,实测一般能够接受,在可控范围内。

3.3 封面页美化与“进入文档”按钮

我之前在给一个小项目做主页时,把封面页做成了一个简洁的产品落地页,效果还不错。分享下_coverpage.md的常见写法:

# DataViz Docs > 轻量、高效的数据可视化平台使用文档 - 五分钟完成接入 - 内置多种图表模板 - 数据权限精细管控 [开始使用](/intro) [查看 API](/api)

封面的背景色和文字样式可以通过自定义 CSS 调整,docsify 的主题包基于 CSS 变量,覆盖不复杂。给个直接能用的例子:

<style> :root { --cover-color: #4e6ef2; --cover-background: #f8f9fb; } main { background: #fff; } </style>

如果希望封面看起来更正式,可以提升背景的留白,把主标语字号加大,按钮颜色改为主色调。总之封面页的可定制程度不低,但不要过度设计,文档站以内容清晰为首要目标。

需要注意一点:docsify 的默认配置里,封面页在点击按钮后会从封面状态“滑动”到主页。如果你希望点击后直接打开新页面,可以用普通的 link 写法,注意 button 样式需要用 docsify 预置的类名。

3.4 多文档项目如何在一个站点里组织

团队里常见的一个需求是:一个站点放多份文档,比如“产品手册”和“开发文档”同时存在,希望用户能在导航里切换。

docsify 处理多文档的方式有两种主流做法:

第一种,把不同文档放在不同目录,侧边栏手动维护两个分区:

docs/ ├── product/ │ ├── README.md │ └── guide.md ├── developer/ │ ├── README.md │ └── api.md ├── _sidebar.md └── index.html

侧边栏这样写:

- 产品手册 - [产品简介](/product/) - [使用指南](/product/guide) - 开发文档 - [开发概览](/developer/) - [API 列表](/developer/api)

这种方式逻辑最简单,适合文档量不大的场景。

第二种,利用 docsify 的alias功能,把一个路径映射到另一个文件。这个功能比较冷门,但特定场景下很好用。比如你想给同一个文档维护多个“入口页面”,但不想复制 Markdown 文件,可以在配置里写:

window.$docsify = { alias: { '/zh-cn/.*': '/zh-cn/README.md' } }

这样访问/zh-cn/下的任意路径,都会加载同一个文件。不过这个功能更多用于多语言场景,如果你没有这个需求,大概率用不到,知道存在即可。

3.5 自定义插件:给 docsify 加一个“编辑本文”按钮

docsify 的插件机制不复杂,官方预留了钩子函数,最常见的hook接口里暴露了afterEachdoneEach等方法。

以添加“编辑本文”按钮为例,在index.html里写一段插件逻辑:

<script> window.$docsify = { plugins: [ function(hook, vm) { hook.beforeEach(function(html) { return html; }); hook.doneEach(function() { var path = vm.route.path; var editLink = 'https://github.com/yourname/yourrepo/edit/master/docs' + path + '.md'; var container = document.querySelector('main section.content'); if (container) { var link = document.createElement('a'); link.href = editLink; link.textContent = '编辑本页'; link.className = 'edit-link'; link.target = '_blank'; container.appendChild(link); } }); } ] }; </script>

这里的逻辑是:每次页面渲染完成后,根据当前路由拼出 GitHub 编辑地址,然后在正文末尾追加一个链接。

需要注意几个细节:

  • vm.route.path获取的是当前路由的路径,前面不带#,但侧面路径要和文档实际位置对应起来,拼 URL 时要留意前缀。
  • hook.doneEach在每次路由切换后都会触发,所以操作 DOM 时不要重复添加节点。可以先用removeEventListener或者检查节点是否存在再插入,避免重复编辑器。
  • 这段代码自己写在index.html里即可,不需要额外打包或构建。

3.6 部署到 Nginx 的配置示例

本地跑通后,部署到服务器的核心是把docs文件夹拷贝到一个静态文件服务里,然后配好路由。

Nginx 的配置可以这样写:

server { listen 80; server_name docs.example.com; root /var/www/docs; index index.html; # docsify 是单页应用,所有路由都指向 index.html location / { try_files $uri $uri/ /index.html; } # 缓存静态资源,减少服务器压力 location ~* \.(js|css|png|jpg|jpeg|gif|svg)$ { expires 7d; add_header Cache-Control "public, no-transform"; } }

这里有一个值得注意的点:docsify 的路由用的是 URL hash(#/xxx),hash 部分不会发送到服务器,所以 Nginx 只需要正确处理根路径即可,try_files主要用来处理首页刷新和静态资源路径。

如果你把 docsify 部署在子路径(比如https://example.com/docs/),需要在index.html里额外配置basePath,否则它默认会从根路径去找 Markdown 文件:

window.$docsify = { basePath: '/docs/' }

这个配置的坑在于,如果你漏掉了它,站点页面可能能打开(因为 index.html 加载正常),但所有 Markdown 文件都会 404,页面内容白屏。排查时看到空白页,第一反应就检查basePath和网络请求里的文件路径是否正确。

4. 常见问题与排查技巧实录

4.1 侧边栏不显示

这个问题的排查顺序非常固定,90% 的情况都能通过以下几步找到原因:

  1. 确认_sidebar.md文件存在于文档根目录,而不是子目录。
  2. 确认index.html配置中有loadSidebar: true
  3. 确认部署平台没有忽略下划线开头的文件(GitHub Pages 加.nojekyll)。
  4. 打开浏览器开发者工具的 Network 标签页,看有没有请求_sidebar.md,如果请求返回 404,说明文件路径不对。

有个隐藏较深的情况:如果你把_sidebar.md放在了某个子目录里,而当前访问的页面在这个子目录下,docsify 会自动向上查找上一级的_sidebar.md。也就是说它不要求侧边栏文件必须出现在每一级目录里。但如果不同子目录要展示不同的侧边栏,需要在对应目录下各自放一份_sidebar.md,docsify 会优先加载当前目录下的文件。

4.2 Markdown 里的相对路径图片加载不出来

在 docsify 里写图片引用时,最常见的错误是使用相对当前 Markdown 文件所在目录的路径,或者直接写绝对根路径。

举例,如果你的文档在docs/guide/目录下,图片放在docs/guide/images/里,那 Markdown 里应该这样写:

![架构图](./images/architecture.png)

而不是:

![架构图](/guide/images/architecture.png)

因为 docsify 对图片的解析是基于当前 Markdown 文件所在目录的。如果写成/guide/...,它会把路径解析成http://localhost:3000/guide/images/architecture.png,而这个路径下并没有文件(文件实际在docs/guide/下),从而 404。

如果你希望所有图片都放到一个统一目录,比如docs/_media/,并在任意页面引用,最简单的方案是在配置里加一个basePath或者使用相对路径../_media/xxx.png,但相对路径层级容易出错。我更推荐的做法是把_media目录放在文档根目录,引用时直接写/_media/xxx.png形式,同时配合 Nginx 里配置 root 到 docs 目录,确保/_media能被正确访问到。这个方案实测下来最少出错。

4.3 搜索索引不更新

搜索索引是存在浏览器本地(localStorage)的,如果你更新了文档内容,但用户端搜索出来的还是旧结果,大概率是索引缓存没失效。

解决办法是在search配置里适当调小maxAge

search: { maxAge: 60000 // 1分钟后重新拉取索引 }

如果是在本地开发环境,想强制清掉索引重新生成,可以在浏览器 Console 里手动执行:

localStorage.removeItem('docsify-search-index')

然后刷新页面,索引会重新构建。

顺便提一句:namespace参数也可以用来“手动换区”,相当于给索引换了一个存储 key,改动之后相当于清空所有旧索引,能解决一些疑难缓存问题。

4.4 部署后刷新返回 404

如果部署到 Nginx 时出现“刷新子页面后 404”,通常是因为 Nginx 配置里缺少try_files。docsify 本身是 hash 路由,严格来说不支持后端路径重写也没有关系,但如果你把 docsify 的index.html当成 SPA 入口,某些安全配置或静态文件服务可能会在子路径下找不到文件。

最简单稳妥的 Nginx 配置前面已给出,关键是try_files $uri $uri/ /index.html;这一行。如果你的环境是 Caddy 或者其他静态服务器,同样需要做类似“所有路径回退到 index.html”的配置。

4.5 代码块复制按钮加不上

docsify 默认没有给代码块提供复制按钮,这是很多写技术文档的人会想要的功能。社区有一个现成插件可以做到:

<script src="//cdn.jsdelivr.net/npm/docsify@4/lib/plugins/copy-code.min.js"></script>

引入后,代码块右上角会自动出现一个复制按钮。但这个插件有个小问题:它会在所有代码块上都加上按钮,包括一些不希望被复制的命令行代码。如果介意,可以自己基于hook.doneEach写一段逻辑,只在特定代码块容器内添加按钮。不过实际体验下来,官方插件的表现基本够用,省心为主。

4.6 多个 docsify 站点共用一个域名

有时候一个域名下部署多个 docsify 文档站点,这时需要注意下面三个配置:

  1. 每个站点前加一个子路径,比如/aa//bb/,对应 Nginx 里不同目录。
  2. 每个站点的index.html里配置basePath指向各自的路径前缀。
  3. 搜索插件的namespace字段各不相同,避免索引串掉。

实测中,最隐蔽的问题是第三个。如果你两个站点共用了相同的namespace,用户的浏览器会把两个站的搜索索引混合在一起,搜索时结果会显示另一个站的文章。当初排查这个问题花了不少时间,最后通过清缓存对比才发现是命名空间撞了。

5. 进阶优化建议

5.1 图片懒加载与资源压缩

如果文档站里图片较多,页面首次打开会加载大量资源。docsify 本身没有内置懒加载,但可以手动写一个简单的插件,在doneEach阶段给图片加loading="lazy"属性:

window.$docsify = { plugins: [ function(hook) { hook.doneEach(function() { document.querySelectorAll('article img').forEach(function(img) { img.setAttribute('loading', 'lazy'); }); }); } ] };

另外,上传图片前尽量压缩一遍。docsify 没有构建阶段,也没有自动压缩图片的能力,图片多大传到服务器就多大,压不压缩就是纯后台功夫。我的习惯是截图后过一遍压缩工具,一般能省一半体积,加载速度提升明显。

5.2 PWA 支持与离线访问

docsify 官方提供了一个 PWA 插件,可以把文档站点做成一个轻量级的离线应用。基本配置是在index.html中引入插件脚本,并设置 manifest 和 Service Worker:

window.$docsify = { pwa: { manifest: { name: 'My Docs', short_name: 'docs', start_url: '/', display: 'standalone', background_color: '#ffffff', theme_color: '#4e6ef2', icons: [ { src: '/images/icon-192.png', sizes: '192x192', type: 'image/png' } ] }, swPath: '/sw.js' } };

启用 PWA 后,用户第一次访问站点时,浏览器会在后台缓存页面资源,后续打开会更快,甚至在离线状态下也能查看已经缓存过的内容。对内部文档来说,这个体验提升还是值得投入的。配置 Service Worker 时尤其注意路径别写错,缓存策略要考虑到文档更新频率,必要时可以设置 cache first 并设置较短的缓存失效时间。

5.3 评论功能的接入

如果你想在文档底部接入评论系统,可以用 docsify 的插件机制接入 Gitalk 或 Valine,配置原理都是在doneEach时初始化评论组件。以 Gitalk 为例:

hook.doneEach(function() { var gitalkDiv = document.getElementById('gitalk-container'); if (gitalkDiv) { gitalkDiv.innerHTML = ''; } else { gitalkDiv = document.createElement('div'); gitalkDiv.id = 'gitalk-container'; document.querySelector('main section.content').appendChild(gitalkDiv); } var gitalk = new Gitalk({ clientID: 'xxx', clientSecret: 'xxx', repo: 'repo', owner: 'owner', admin: ['owner'], id: location.hash, // 用路由 hash 作为评论标识 title: document.title }); gitalk.render('gitalk-container'); });

这个方案的坑点在于,Gitalk 的评论主题是基于 GitHub issue 的,每篇文章需要对应一个 issue。可以用id参数来生成稳定的标识,但不建议直接用整段location.hash,因为 hash 里带特殊字符时 GitHub 可能报 422。更好的做法是用一个简单路由路径去映射,比如去掉前缀和特殊字符后的location.hash.substring(2)或对整个 hash 做一次简单编码。

评论组件接入后,要特别注意在每次路由切换时清空旧容器,否则会重复初始化。上面的代码里已经做了innerHTML = ''的清理,可以根据实际需要再完善。

5.4 定制主题样式

docsify 默认提供 three 套主题,分别是vue.cssbuble.cssdark.css。日常文档站大多用的是vue.css。如果要定制品牌色,可以在 index.html 里覆盖 CSS 变量,比如:

<style> :root { --theme-color: #ff6d00; --heading-color: #333; --text-color: #444; --sidebar-background: #f5f6f7; } </style>

实测下来,docsify 主题的 CSS 变量覆盖机制比传统重写 CSS 类优雅很多,只需要关注几个核心变量即可。不需要 SASS 预处理器,不用额外编译,就是纯 CSS 变量。如果动手能力比较强,甚至可以基于 docsify 的样式自定义一套完整的卡片式文档风格。

6. 性能和SEO相关的一点补充

6.1 首屏加载优化

因为 docsify 是运行时解析 Markdown,首屏加载必须等 JS 执行完、Markdown 文件拉取完才能显示内容,所以首屏体验天然比静态 HTML 慢一点。

几个有效的优化措施:

  • 使用 CDN 加载 docsify 核心文件,不要自托管一份,除非你对内网强依赖。
  • 将文档图片放到图床或 CDN,尽量减轻服务器压力。
  • 关闭不需要的插件,插件越多,初始化的 JS 越大。
  • 如果只是展示型文档,可以用预取或 preload 提升体验,docsify 4 版本内置了一些优化机制,不需要额外配置。

还有一个容易被忽略的点:index.html<div id="app">加载中...</div>这段文字,会在页面加载时显示。可以改成更友好的加载动画或者一句说明文字,避免用户看着白屏以为网站挂了。

6.2 关于 SEO 的补救措施

docsify 的短板就是 SEO。页面内容靠 JS 动态渲染,搜索引擎爬虫如果不执行 JS,就抓不到文档内容。

目前常用的补救方案有两种:

第一种是通过 SSR 预渲染。比如在部署前用一个脚本,模拟浏览器访问所有页面,把渲染后的 HTML 结果保存成静态文件,替换掉原本的空 HTML。这个做法实际上又回到了“静态生成”,会引入构建步骤,但比较灵活,可以做成一个单独的“SEO 版本”目录,只在需要给爬虫看时启用。

第二种是使用服务端渲染代理,对爬虫的 User-Agent 返回渲染后的页面。这类服务的实现稍微复杂,内部使用不多。

我的建议是:如果站点确实需要大量来自搜索引擎的自然流量,那么 docsify 不是最优选择,一开始就应该选一个 SSR 框架。如果只是给内部团队或者已有用户群体用的文档,完全不需要为 SEO 花太多精力,保持简单才是核心价值。

6.3 多语言支持

docsify 官方支持多语言。常见的做法是在项目里维护/zh-cn//en/两套 Markdown 目录,然后利用alias或多目录结构组织。

如果你只需要同时展示两种语言,可以用“切换导航”的方式:

- [中文](/) - [English](/en/)

其中/en/子目录下放置英文文档,每个文件对应中文页面。docsify 默认不会自动做“相同路径、不同语言”的映射,所以需要你在index.html里用alias或手动链接来组织。

多语言维护成本不低,建议文档规模较小时尽量先集中精力写一种语言,等内容趋于稳定后再扩展语言版本。

7. 我实际使用中的几个体会

最后分享几个长期使用 docsify 后形成的习惯,可能对你有参考价值。

第一,我把 docsify 站点的文档目录和代码仓库放在同一个仓库,这样 Markdown 文件和代码改动能够保持同步。工程师在改代码的时候顺手改文档,提交到同一个 PR 里,文档不容易过期。有些人喜欢单独开一个 docs 仓库,好处是职责清晰,坏处是维护成本高,容易“文档仓库没人管”。我倾向于“文档跟着代码走”,至少对于核心项目是这样。

第二,_sidebar.md建立状态管理。当文档页数多了以后,侧边栏会变得又长又乱。我会定期整理一次,把 “常用”和“不常用”的页面分开,不常用的折叠进一个 “其他” 分组。docsify 默认不能折叠侧边栏,但可以先在 Markdown 层面做好分组,让读者视觉上不容易迷失。

第三,用 GitHub Actions 做自动发布。虽然 docsify 没有构建步骤,但仍然可以借助 GitHub Actions 在每次推送 main 分支后自动把docs目录同步到服务器或者某个线上目录,省去手动上传的环节。因为流程里没有构建这一步,整个 CI 脚本能写得非常短,这也是我用过的所有文档方案里面 CI 最简洁的一种。

name: deploy-docs on: push: branches: [main] jobs: deploy: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: Deploy to Server uses: easingthemes/ssh-deploy@main with: SSH_PRIVATE_KEY: ${{ secrets.SSH_PRIVATE_KEY }} ARGS: "-avz --delete" REMOTE_HOST: ${{ secrets.REMOTE_HOST }} REMOTE_USER: ${{ secrets.REMOTE_USER }} TARGET: /var/www/docs SOURCE: "docs/"

这个 workflow 的唯一作用就是“上传文件”,没有安装依赖、没有编译、没有测试环节。运行一次只要几十秒,比大部分静态站生成器的 CI 都要简单。你在实际使用时,直接改成自己的服务器信息即可。

如果你正在找一个团队内部或开源项目里“轻到不能再轻”的文档方案,docsify 值得认真试一试。它不适合所有人,但它的设计哲学——把写文档的摩擦降到最低——至少值得你体验一次。假如你之前一直用 VuePress 和 GitBook,不妨在本机花五分钟跑一遍 docsify,感受一下改动即刷新的速度,也许你会和我一样,在那个瞬间就决定把手头下一个项目换成它。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询