普通视图

发现新文章,点击刷新页面。
昨天以前首页

开源项目文档架构设计:Git Submodule 实现文档与代码的优雅分离

作者 Carsene
2026年3月26日 13:48

前言

在开源项目的维护过程中,你是否遇到过这样的困扰:文档更新频繁触发主项目的 CI/CD 流程?文档部署配置与代码构建配置相互干扰?文档版本与代码版本难以同步?

本文将分享一个优雅的解决方案:使用 Git Submodule 将文档独立为单独仓库,实现文档的独立部署和版本管理,同时保持与主项目的关联。

问题背景

当前架构的问题

在 AutoScan 项目的早期,文档直接放在主项目仓库中:

autoscan-spring-boot-starter/
├── docs/
│   └── zh/
│       ├── index.html
│       ├── version.js
│       └── *.md
├── src/
├── pom.xml
└── README.md

这种架构带来了一系列问题:

问题 1:CI/CD 流程干扰

# 主项目的 GitHub Actions
on:
  push:
    paths:
      - 'src/**'
      - 'pom.xml'
      # 文档更新也会触发构建 ❌

每次更新文档都会触发主项目的构建流程,浪费 CI/CD 资源。

问题 2:部署配置冲突

主项目需要:
- Maven 构建
- 单元测试
- 发布到 Maven Central

文档需要:
- GitHub Pages 部署
- 自定义域名
- HTTPS 配置

两种完全不同的部署需求在同一个仓库中配置,容易产生冲突。

问题 3:版本管理困难

主项目版本:v1.1.0
文档版本:v1.1.0
文档更新后:v1.1.0 (文档已更新,但代码未变)

文档和代码耦合在同一仓库,版本对应关系不清晰。

解决方案演进

方案 1:文档在主仓库内(当前方案)

autoscan-spring-boot-starter/
├── docs/
│   └── zh/
├── src/
└── pom.xml

优点

  • ✅ 文档和代码版本同步
  • ✅ 简单直接,无需额外管理

缺点

  • ❌ 文档更新会触发主项目的 CI/CD
  • ❌ 文档和代码耦合
  • ❌ GitHub Pages 部署需要特殊配置

方案 2:文档单独仓库(不使用 Submodule)

autoscan-spring-boot-starter/ (主项目)
├── src/
└── pom.xml

autoscan-docs/ (独立仓库)
├── zh/
└── index.html

优点

  • ✅ 完全独立,最简单
  • ✅ 各自独立部署

缺点

  • ❌ 文档和代码完全分离
  • ❌ 版本同步困难
  • ❌ 无法追溯特定版本的文档

方案 3:文档单独仓库 + Git Submodule(推荐方案)

autoscan-spring-boot-starter/
├── docs/ (submodule -> autoscan-docs)
├── src/
└── pom.xml

autoscan-docs/ (独立仓库)
├── zh/
│   ├── index.html
│   ├── version.js
│   └── *.md
└── README.md

优点

  • ✅ 文档独立部署和更新
  • ✅ 主项目不受文档更新影响
  • ✅ 可以独立配置文档的 CI/CD
  • ✅ GitHub Pages / Cloudflare Pages 可以直接监听文档仓库
  • ✅ 保持文档和代码的关联
  • ✅ 可以追溯特定版本的文档

缺点

  • ⚠️ 需要管理 submodule
  • ⚠️ 操作复杂度略增

推荐方案详解

架构设计

┌─────────────────────────────────────────┐
│     autoscan-spring-boot-starter        │
│         (主项目仓库)                      │
│                                         │
│  ┌─────────────────────────────────┐   │
│  │  docs/ (submodule)              │   │
│  │  └─> 指向 autoscan-docs 仓库     │   │
│  └─────────────────────────────────┘   │
│                                         │
│  src/                                   │
│  pom.xml                                │
│  README.md                              │
└─────────────────────────────────────────┘
                    │
                    │ submodule 引用
                    ▼
┌─────────────────────────────────────────┐
│          autoscan-docs                  │
│         (文档仓库)                        │
│                                         │
│  zh/                                    │
│  ├── index.html                         │
│  ├── version.js                         │
│  └── *.md                               │
│                                         │
│  .github/workflows/                     │
│  ├── update-doc-version.yml             │
│  └── deploy.yml                         │
└─────────────────────────────────────────┘

核心优势

1. 独立部署

文档仓库可以独立部署到 GitHub Pages 或 Cloudflare Pages:

# autoscan-docs/.github/workflows/deploy.yml
name: Deploy to GitHub Pages

on:
  push:
    branches:
      - main

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v4
      
      - name: Deploy to GitHub Pages
        uses: peaceiris/actions-gh-pages@v3
        with:
          github_token: ${{ secrets.GITHUB_TOKEN }}
          publish_dir: ./

2. CI/CD 独立

文档更新不会触发主项目的构建:

# 主项目的 GitHub Actions
on:
  push:
    paths:
      - 'src/**'
      - 'pom.xml'
      # 不包含 docs/ 目录

3. 版本关联

通过 submodule 保持文档和代码的版本对应:

# 主项目 v1.1.0 引用文档仓库的某个 commit
git submodule add https://github.com/itrys/autoscan-docs.git docs

# 发布 v1.2.0 时,更新 submodule 引用
git submodule update --remote

具体实施步骤

步骤 1:创建文档仓库

# 创建新的文档仓库
mkdir autoscan-docs
cd autoscan-docs
git init

# 创建目录结构
mkdir -p zh/js

# 创建文件
touch zh/index.html
touch zh/version.js
touch zh/_sidebar.md
touch zh/_coverpage.md

# 提交
git add .
git commit -m "init: 初始化文档仓库"

# 推送到远程
git remote add origin https://github.com/itrys/autoscan-docs.git
git push -u origin main

步骤 2:迁移文档内容

# 从主项目复制文档文件
cp -r ../autoscan-spring-boot-starter/docs/zh/* zh/

# 提交
git add .
git commit -m "docs: 迁移文档内容"
git push

步骤 3:在主项目中添加 Submodule

# 进入主项目目录
cd ../autoscan-spring-boot-starter

# 删除原来的 docs 目录
rm -rf docs

# 添加 submodule
git submodule add https://github.com/itrys/autoscan-docs.git docs

# 提交
git add .
git commit -m "chore: 使用 submodule 引用文档仓库"
git push

步骤 4:配置文档仓库的 GitHub Actions

创建 .github/workflows/update-doc-version.yml

name: Update Doc Version

on:
  push:
    paths:
      - 'zh/*.md'
    branches:
      - main

jobs:
  update-version:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v4
        with:
          fetch-depth: 2
      
      - name: Update timestamp
        run: |
          TIMESTAMP=$(date +%Y%m%d%H%M)
          sed -i "s/window.DOC_TIMESTAMP = '.*'/window.DOC_TIMESTAMP = '$TIMESTAMP'/" zh/version.js
          echo "Updated timestamp to: $TIMESTAMP"
      
      - name: Commit changes
        run: |
          git config --local user.email "github-actions[bot]@users.noreply.github.com"
          git config --local user.name "github-actions[bot]"
          git add zh/version.js
          git commit -m "chore: auto-update doc timestamp [skip ci]" || echo "No changes to commit"
          git push

创建 .github/workflows/deploy.yml

name: Deploy to GitHub Pages

on:
  push:
    branches:
      - main
  workflow_dispatch:

permissions:
  contents: read
  pages: write
  id-token: write

concurrency:
  group: "pages"
  cancel-in-progress: false

jobs:
  deploy:
    environment:
      name: github-pages
      url: ${{ steps.deployment.outputs.page_url }}
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v4
      
      - name: Setup Pages
        uses: actions/configure-pages@v4
      
      - name: Upload artifact
        uses: actions/upload-pages-artifact@v3
        with:
          path: '.'
      
      - name: Deploy to GitHub Pages
        id: deployment
        uses: actions/deploy-pages@v4

步骤 5:更新主项目的 README

在主项目的 README.md 中添加文档链接:

## 📚 文档

- [在线文档](https://itrys.github.io/autoscan-docs/)
- [快速开始](https://itrys.github.io/autoscan-docs/#/quickstart)
- [功能特性](https://itrys.github.io/autoscan-docs/#/features)

文档源码:[autoscan-docs](https://github.com/itrys/autoscan-docs)

Git Submodule 常用操作

克隆包含 submodule 的项目

# 方法1:递归克隆(推荐)
git clone --recursive https://github.com/itrys/autoscan-spring-boot-starter.git

# 方法2:先克隆再初始化
git clone https://github.com/itrys/autoscan-spring-boot-starter.git
cd autoscan-spring-boot-starter
git submodule init
git submodule update

# 方法3:在已克隆的项目中初始化 submodule
git submodule update --init --recursive

更新 submodule

# 更新 submodule 到最新版本
git submodule update --remote

# 更新特定 submodule
git submodule update --remote docs

# 在 submodule 目录中操作
cd docs
git pull origin main
cd ..
git add docs
git commit -m "docs: 更新文档"

查看 submodule 状态

# 查看 submodule 状态
git submodule status

# 查看 submodule 详细信息
git submodule

# 查看 submodule 的远程仓库
cd docs
git remote -v

删除 submodule

# 删除 submodule
git submodule deinit docs
git rm docs
rm -rf .git/modules/docs
git commit -m "chore: 移除文档 submodule"

版本同步策略

发布新版本时的流程

# 1. 更新主项目代码
vim src/main/java/...

# 2. 更新文档
cd docs
# 修改文档内容
vim zh/quickstart.md

# 更新版本号
vim zh/version.js
# 修改:window.DOC_VERSION = '1.2.0';

git add .
git commit -m "docs: 更新 v1.2.0 文档"
git push

# 3. 回到主项目,更新 submodule 引用
cd ..
git add docs
git commit -m "release: v1.2.0"
git push

版本对应关系

主项目 Commit    →  文档仓库 Commit
v1.1.0 (abc123)  →  docs v1.1.0 (def456)
v1.2.0 (ghi789)  →  docs v1.2.0 (jkl012)

通过 submodule,可以精确追溯每个版本对应的文档内容。

最佳实践

1. 使用 Tag 标记版本

在文档仓库中打标签:

# 在文档仓库
cd autoscan-docs
git tag -a v1.1.0 -m "文档 v1.1.0"
git push origin v1.1.0

# 在主项目中引用特定标签
cd autoscan-spring-boot-starter
cd docs
git checkout v1.1.0
cd ..
git add docs
git commit -m "docs: 引用文档 v1.1.0"

2. 使用分支管理版本

# 为文档创建版本分支
cd autoscan-docs
git checkout -b v1.1.x
git push origin v1.1.x

# 主项目引用分支
cd autoscan-spring-boot-starter
git config -f .gitmodules submodule.docs.branch v1.1.x
git submodule update --remote

3. 自动化版本同步

创建 GitHub Actions 自动更新 submodule 引用:

# 主项目的 .github/workflows/update-docs.yml
name: Update Docs Submodule

on:
  repository_dispatch:
    types: [docs-updated]

jobs:
  update-submodule:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v4
        with:
          submodules: true
      
      - name: Update submodule
        run: |
          git submodule update --remote
      
      - name: Commit changes
        run: |
          git config --local user.email "github-actions[bot]@users.noreply.github.com"
          git config --local user.name "github-actions[bot]"
          git add docs
          git commit -m "docs: 更新文档引用" || echo "No changes"
          git push

4. 文档仓库的 README

在文档仓库中添加 README,说明文档与主项目的关系:

# AutoScan 文档

本仓库是 [AutoScan Spring Boot Starter](https://github.com/itrys/autoscan-spring-boot-starter) 的文档仓库。

## 文档版本

| 版本 | 主项目版本 | 更新内容 |
|------|-----------|----------|
| v1.1.0 | v1.1.0 | 新增通配符、排除扫描、自定义注解功能 |
| v1.0.0 | v1.0.0 | 初始版本 |

## 本地预览

```bash
# 克隆仓库
git clone https://github.com/itrys/autoscan-docs.git
cd autoscan-docs

# 启动本地服务器
python -m http.server 8000
# 或使用 docsify-server
npm install -g docsify-cli
docsify serve

贡献指南

请参考 主项目贡献指南


## 常见问题

### Q1: Submodule 更新后,团队成员如何同步?

```bash
# 团队成员拉取最新代码时
git pull
git submodule update --init --recursive

# 或者使用一条命令
git pull --recurse-submodules

Q2: 如何避免 Submodule 的常见错误?

# 配置 Git 自动更新 submodule
git config --global submodule.recurse true

# 配置 Git 在状态检查时包含 submodule
git config --global status.submoduleSummary true

Q3: 如何在 CI/CD 中正确处理 Submodule?

# GitHub Actions
- name: Checkout
  uses: actions/checkout@v4
  with:
    submodules: true  # 自动初始化和更新 submodule

方案对比总结

方案 独立部署 版本关联 CI/CD 独立 维护成本 推荐度
文档在主仓库 ⭐⭐
文档单独仓库 ⭐⭐⭐
Submodule ⭐⭐⭐⭐⭐

总结

通过 Git Submodule 将文档独立为单独仓库,我们实现了:

核心优势

  1. 独立部署 - 文档可以独立部署到 GitHub Pages / Cloudflare Pages
  2. 版本关联 - 通过 submodule 保持文档和代码的版本对应
  3. CI/CD 独立 - 文档更新不会触发主项目构建
  4. 灵活管理 - 可以独立配置文档的自动化流程
  5. 版本追溯 - 可以精确追溯每个版本对应的文档内容

适用场景

  • ✅ 中大型开源项目
  • ✅ 文档更新频繁的项目
  • ✅ 需要独立部署文档的项目
  • ✅ 多版本文档管理需求

这个方案完美解决了文档与代码耦合的问题,既保持了独立性,又维持了关联性,是开源项目文档管理的最佳实践之一。

参考资源


如果这篇文章对你有帮助,欢迎点赞、收藏、关注! 🎉

Docsify 文档缓存问题终极解决方案:拦截请求自动添加版本号

作者 Carsene
2026年3月26日 09:58

📋 问题背景

在使用 Docsify 搭建文档网站时,你是否遇到过这样的情况:明明已经更新了文档内容,但用户刷新页面后看到的仍然是旧内容

这是一个典型的浏览器缓存问题,困扰着许多 Docsify 用户。本文将深入分析问题原因,并提供一个终极解决方案。

🔍 问题分析

缓存问题的根源

  1. 浏览器缓存机制:浏览器会缓存静态资源(包括 .md 文件)以提高性能
  2. CDN 缓存:如果使用 CDN 加速,CDN 节点也会缓存文件
  3. 缓存失效机制:默认情况下,浏览器只有在资源过期或 URL 变化时才会重新请求

常见解决方案的局限性

方案 优点 缺点
手动清除缓存 简单直接 用户体验差,需要用户操作
Nginx 设置 no-cache 服务器端控制 需要服务器配置权限,影响所有资源
文件名加 hash 彻底解决 需要构建工具支持,增加部署复杂度
URL 加时间戳 简单有效 每次都重新加载,影响性能
只加版本号 版本管理清晰 同一版本内更新不生效

🎯 最佳实践:版本号 + 时间戳组合方案

核心思路

拦截 Docsify 的 MD 文件请求,自动添加版本号 + 时间戳参数

README.md → README.md?v=1.1.0_202603260852

设计理念

  • 版本号:用于大版本更新,保证版本管理清晰
  • 时间戳:用于同一版本内的频繁更新,确保缓存及时失效
  • 组合使用:兼顾版本管理和缓存控制的需求

技术实现

方案 A:内联代码(适合小型项目)

优点:简单直接,无需额外文件 缺点:版本信息分散,不便于自动化

<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <title>文档标题</title>
  <!-- 其他 head 内容 -->
</head>
<body>
  <div id="app"></div>
  
  <!-- 版本控制和请求拦截 -->
  <script>
    // 版本控制配置
    const DOC_VERSION = '1.1.0';  // 版本号
    const DOC_TIMESTAMP = '202603260852';  // 时间戳
    const DOC_CACHE_KEY = DOC_VERSION + '_' + DOC_TIMESTAMP;
    
    // 重写 XMLHttpRequest
    const originalOpen = XMLHttpRequest.prototype.open;
    XMLHttpRequest.prototype.open = function(method, url) {
      let modifiedUrl = url;
      if (typeof url === 'string' && url.endsWith('.md') && !url.includes('v=')) {
        const separator = url.includes('?') ? '&' : '?';
        modifiedUrl = url + separator + 'v=' + DOC_CACHE_KEY;
      }
      // 使用 arguments 传递所有参数,避免参数丢失
      arguments[1] = modifiedUrl;
      return originalOpen.apply(this, arguments);
    };
    
    // 重写 fetch
    if (window.fetch) {
      const originalFetch = window.fetch;
      window.fetch = function(input, init) {
        let url = input;
        if (typeof input === 'string' && input.endsWith('.md') && !input.includes('v=')) {
          const separator = input.includes('?') ? '&' : '?';
          url = input + separator + 'v=' + DOC_CACHE_KEY;
        }
        return originalFetch.call(this, url, init);
      };
    }
  </script>
  
  <!-- Docsify 配置 -->
  <script>
    window.$docsify = {
      // 你的 docsify 配置
    }
  </script>
  
  <!-- 加载 Docsify -->
  <script src="//cdn.bootcdn.net/ajax/libs/docsify/4.13.1/docsify.min.js"></script>
</body>
</html>

方案 B:version.js 文件(推荐,适合中大型项目)

优点:集中管理,便于自动化,团队协作友好 缺点:需要处理自身缓存,多一个文件

步骤 1:创建 version.js 文件
// version.js
// 文档版本控制配置
const DOC_VERSION = '1.1.0';  // 版本号:发布新版本时更新
const DOC_TIMESTAMP = '202603260852';  // 时间戳:同一版本内更新时修改
const DOC_CACHE_KEY = DOC_VERSION + '_' + DOC_TIMESTAMP;

// 重写 XMLHttpRequest
const originalOpen = XMLHttpRequest.prototype.open;
XMLHttpRequest.prototype.open = function(method, url) {
  let modifiedUrl = url;
  if (typeof url === 'string' && url.endsWith('.md') && !url.includes('v=')) {
    const separator = url.includes('?') ? '&' : '?';
    modifiedUrl = url + separator + 'v=' + DOC_CACHE_KEY;
  }
  arguments[1] = modifiedUrl;
  return originalOpen.apply(this, arguments);
};

// 重写 fetch
if (window.fetch) {
  const originalFetch = window.fetch;
  window.fetch = function(input, init) {
    let url = input;
    if (typeof input === 'string' && input.endsWith('.md') && !input.includes('v=')) {
      const separator = input.includes('?') ? '&' : '?';
      url = input + separator + 'v=' + DOC_CACHE_KEY;
    }
    return originalFetch.call(this, url, init);
  };
}
步骤 2:加载 version.js(解决自身缓存问题)
<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <title>文档标题</title>
  <!-- 其他 head 内容 -->
</head>
<body>
  <div id="app"></div>
  
  <!-- 加载版本配置和请求拦截 -->
  <script>
    // 动态创建 script 标签加载 version.js,添加时间戳防止缓存
    const script = document.createElement('script');
    script.src = 'version.js?v=' + Date.now();
    document.body.appendChild(script);
  </script>
  
  <!-- Docsify 配置 -->
  <script>
    window.$docsify = {
      // 你的 docsify 配置
    }
  </script>
  
  <!-- 加载 Docsify -->
  <script src="//cdn.bootcdn.net/ajax/libs/docsify/4.13.1/docsify.min.js"></script>
</body>
</html>

⚠️ 技术陷阱与解决方案

陷阱 1:页面空白,内容不显示

问题原因:重写 XMLHttpRequest 时参数丢失

错误代码

// ❌ 错误:显式声明参数,导致额外参数丢失
XMLHttpRequest.prototype.open = function(method, url, async, user, password) {
  return originalOpen.call(this, method, modifiedUrl, async, user, password);
}

解决方案

// ✅ 正确:使用 arguments 传递所有参数
XMLHttpRequest.prototype.open = function(method, url) {
  let modifiedUrl = url;
  // ... 修改 url
  arguments[1] = modifiedUrl;  // 只修改 url 参数
  return originalOpen.apply(this, arguments);  // 传递完整参数列表
}

陷阱 2:只重写 fetch 无效

问题原因:Docsify 4.x 默认使用 XMLHttpRequest

解决方案:同时重写 XMLHttpRequest 和 fetch,确保兼容性

陷阱 3:version.js 自身缓存

问题原因:version.js 文件本身也会被浏览器缓存

解决方案:使用动态脚本加载,添加时间戳参数

script.src = 'version.js?v=' + Date.now();

陷阱 4:执行顺序错误

问题原因:在 Docsify 加载后才重写请求方法

解决方案:在 Docsify 加载前重写请求方法

📊 效果验证

使用前

请求:README.md
状态:200 OK (from disk cache)
结果:显示旧内容

使用后

请求:README.md?v=1.1.0_202603260852
状态:200 OK
结果:显示最新内容

🚀 实际应用指南

方案选择

项目规模 推荐方案 原因
小型项目 内联代码 简单直接,无需额外文件
中大型项目 version.js 集中管理,便于自动化

更新流程

场景 1:发布新版本

  1. 修改版本号(如 1.1.0 → 1.2.0)
  2. 重置时间戳

场景 2:同一版本内更新

  1. 保持版本号不变
  2. 更新时间戳(如 202603260852 → 202603261000)

自动化配置

创建 GitHub Actions 工作流,自动更新时间戳:

name: Update Doc Version

on:
  push:
    paths:
      - 'docs/**/*.md'
    branches:
      - main

jobs:
  update-version:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v4
      
      - name: Update timestamp
        run: |
          TIMESTAMP=$(date +%Y%m%d%H%M)
          sed -i "s/const DOC_TIMESTAMP = '.*'/const DOC_TIMESTAMP = '$TIMESTAMP'/" docs/zh/version.js
          sed -i "s/const DOC_TIMESTAMP = '.*'/const DOC_TIMESTAMP = '$TIMESTAMP'/" docs/en/version.js
      
      - name: Commit changes
        run: |
          git config --local user.email "github-actions[bot]@users.noreply.github.com"
          git config --local user.name "github-actions[bot]"
          git add docs/zh/version.js docs/en/version.js
          git commit -m "chore: auto-update doc timestamp" || echo "No changes"
          git push

🎓 技术深度解析

1. XMLHttpRequest 重写原理

XMLHttpRequest.prototype.open 是一个原型方法,我们通过修改它来拦截所有 XHR 请求。使用 arguments 对象可以保留所有原始参数,避免参数丢失。

2. 为什么需要同时重写 fetch?

  • 兼容性:不同浏览器和 Docsify 版本可能使用不同的请求方式
  • 未来-proof:现代浏览器和未来版本的 Docsify 可能更多使用 fetch
  • 完整性:确保所有 MD 文件请求都被拦截

3. 版本号 + 时间戳的设计哲学

  • 版本号:提供语义化的版本管理,便于团队协作和问题追踪
  • 时间戳:提供细粒度的缓存控制,确保及时更新
  • 组合使用:在性能和更新及时性之间取得最佳平衡

4. 性能影响评估

  • 正面影响:同一版本内的重复访问会使用缓存,提高性能
  • 负面影响:版本更新时会重新加载所有 MD 文件
  • 总体评估:利大于弊,用户体验得到显著改善

📝 最佳实践总结

  1. 选择合适的方案:根据项目规模选择内联代码或 version.js
  2. 正确重写请求方法:使用 arguments 传递所有参数
  3. 同时支持两种请求方式:重写 XMLHttpRequest 和 fetch
  4. 处理 version.js 自身缓存:使用动态加载 + 时间戳
  5. 配置自动化更新:集成 GitHub Actions 自动更新时间戳
  6. 监控和测试:定期检查缓存效果,确保方案有效

🔧 代码优化建议

1. 错误处理

// 增强版:添加错误处理
XMLHttpRequest.prototype.open = function(method, url) {
  try {
    let modifiedUrl = url;
    if (typeof url === 'string' && url.endsWith('.md') && !url.includes('v=')) {
      const separator = url.includes('?') ? '&' : '?';
      modifiedUrl = url + separator + 'v=' + DOC_CACHE_KEY;
    }
    arguments[1] = modifiedUrl;
    return originalOpen.apply(this, arguments);
  } catch (error) {
    console.error('[AutoScan Docs] XHR rewrite error:', error);
    return originalOpen.apply(this, arguments);
  }
};

2. 配置灵活性

// 增强版:可配置的文件扩展名
const CACHE_CONFIG = {
  extensions: ['.md', '.markdown'],
  version: '1.1.0',
  timestamp: '202603260852'
};

const DOC_CACHE_KEY = CACHE_CONFIG.version + '_' + CACHE_CONFIG.timestamp;

// 检查文件扩展名
const shouldAddVersion = (url) => {
  return CACHE_CONFIG.extensions.some(ext => url.endsWith(ext));
};

🎯 最终效果

通过本文介绍的方案,你可以:

  • ✅ 彻底解决 Docsify 文档缓存问题
  • ✅ 确保用户始终看到最新的文档内容
  • ✅ 保持良好的性能和用户体验
  • ✅ 便于版本管理和团队协作
  • ✅ 支持自动化更新流程

无论是个人项目还是企业级应用,这个方案都能为你提供一个可靠、高效的 Docsify 文档缓存解决方案。


相关资源


希望本文对你解决 Docsify 文档缓存问题有所帮助!如果你有任何问题或建议,欢迎在评论区留言。🎉

Docsify + Nginx 部署指南:解决 404 路由与 Markdown 加载失败问题

作者 Carsene
2026年3月24日 09:45

摘要:使用 Docsify 构建文档站点轻量又高效,但在部署到 Nginx 时,常遇到“首页能打开,点击导航后内容 404”或“无法加载 README.md”等问题。本文深入剖析原因,并提供完整、可靠的 Nginx 配置方案与调试技巧。


一、为什么 Docsify 需要特殊 Nginx 配置?

Docsify 是一个基于前端路由的单页应用(SPA) ,它不生成静态 HTML,而是通过 JavaScript 动态加载 Markdown 文件并渲染页面。

这意味着:

  • 用户访问 /guide/ 时,浏览器实际加载的是 index.html
  • Docsify 在前端解析路由,并发起 AJAX 请求获取 /guide/README.md
  • 如果用户直接刷新页面从外部链接进入 /guide/,服务器必须仍返回 index.html,否则会 404。

这与传统静态站点(如 Hugo、Jekyll)完全不同——后者每个页面都有真实 HTML 文件。


二、典型问题现象

  1. 首页正常,点击导航后空白或 404

    • 浏览器地址栏变为 /guide/,但页面无内容。
  2. 控制台报错:Failed to load resource: the server responded with a status of 404

    • 通常是 README.md_sidebar.md 等文件加载失败。
  3. 手动访问 .md 文件返回 404 或被拒绝

这些问题的根源往往在于 Nginx 配置未正确区分“前端路由”和“静态资源”


三、正确 Nginx 配置(关键!)

✅ 基础配置模板

server {
    listen 80;
    server_name your-domain.com;
    root /var/www/docs;  # 指向包含 index.html 的目录
    index index.html;

    # 允许 .md 文件作为纯文本返回(可选但推荐)
    location ~* .md$ {
        default_type text/markdown;
        add_header Content-Type text/markdown;
    }

    # 核心:SPA 路由回退机制
    location / {
        try_files $uri $uri/ /index.html;
    }

    # 可选:缓存静态资源
    location ~* .(js|css|png|jpg|svg)$ {
        expires 1y;
        add_header Cache-Control "public, immutable";
    }
}

🔍 try_files 工作原理

try_files $uri $uri/ /index.html;

Nginx 按顺序尝试:

  1. $uri → 是否存在同名文件?(如 /style.css/guide/README.md
  2. $uri/ → 是否存在同名目录?(如 /guide/ 目录)
  3. 都不存在 → 返回 /index.html,交由 Docsify 前端处理路由

只要 .md 文件物理存在,就不会触发 fallback!


四、常见错误排查清单

问题 检查点 解决方案
.md 文件 404 文件是否真实存在于 root 目录下? 确认目录结构,如 /var/www/docs/guide/README.md
.md 被禁止访问 是否有 location ~ .(md|txt)$ { deny all; } 删除或注释掉该规则
请求路径错误 Docsify 的 basePath 是否配置错误? 若部署在根路径,设为 basePath: '/' 或省略
Nginx root 错误 root 是否指向了包含 index.html 的父目录? 确保 root /path/to/site; 下有 index.html

五、调试技巧

  1. 查看 Network 请求

    • 打开 DevTools → Network → 刷新页面
    • 查看 README.md_sidebar.md 的请求 URL 和状态码
  2. 手动测试 .md 文件

    • 在浏览器中直接访问:https://your-site.com/guide/README.md
    • 应能下载或显示 Markdown 内容
  3. 检查 Docsify 初始化代码

    <script>
      window.$docsify = {
        // basePath: '/docs/',  // ❌ 除非你部署在子路径
        loadSidebar: true,
        name: 'My Docs'
      }
    </script>
    

六、最佳实践建议

  • 不要用 alias 代替 root:容易导致路径混乱。
  • 避免在 Nginx 中重写 .md 请求:让 Docsify 自己管理 Markdown 加载。
  • 启用 Gzip 压缩:提升加载速度(Nginx 默认支持)。
  • 添加 nojekyll 文件:如果你也考虑 GitHub Pages,可保留兼容性。

七、结语

Docsify 的魅力在于“零构建、实时渲染”,但这也对服务器配置提出了 SPA 式的要求。只要理解 “前端路由 vs 静态资源” 的区别,并正确配置 try_files,就能轻松部署一个高性能、可维护的文档站点。

🌐 你的文档值得被优雅地呈现 —— 从正确的 Nginx 配置开始。


延伸阅读

❌
❌