📋 问题背景
在使用 Docsify 搭建文档网站时,你是否遇到过这样的情况:明明已经更新了文档内容,但用户刷新页面后看到的仍然是旧内容?
这是一个典型的浏览器缓存问题,困扰着许多 Docsify 用户。本文将深入分析问题原因,并提供一个终极解决方案。
🔍 问题分析
缓存问题的根源
- 浏览器缓存机制 :浏览器会缓存静态资源(包括
.md文件)以提高性能 - CDN 缓存:如果使用 CDN 加速,CDN 节点也会缓存文件
- 缓存失效机制:默认情况下,浏览器只有在资源过期或 URL 变化时才会重新请求
常见解决方案的局限性
| 方案 | 优点 | 缺点 |
|---|---|---|
| 手动清除缓存 | 简单直接 | 用户体验差,需要用户操作 |
| Nginx 设置 no-cache | 服务器端控制 | 需要服务器配置权限,影响所有资源 |
| 文件名加 hash | 彻底解决 | 需要构建工具支持,增加部署复杂度 |
| URL 加时间戳 | 简单有效 | 每次都重新加载,影响性能 |
| 只加版本号 | 版本管理清晰 | 同一版本内更新不生效 |
🎯 最佳实践:版本号 + 时间戳组合方案
核心思路
拦截 Docsify 的 MD 文件请求,自动添加版本号 + 时间戳参数:
ini
README.md → README.md?v=1.1.0_202603260852
设计理念:
- 版本号:用于大版本更新,保证版本管理清晰
- 时间戳:用于同一版本内的频繁更新,确保缓存及时失效
- 组合使用:兼顾版本管理和缓存控制的需求
技术实现
方案 A:内联代码(适合小型项目)
优点 :简单直接,无需额外文件 缺点:版本信息分散,不便于自动化
html
<!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 文件
javascript
// 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(解决自身缓存问题)
html
<!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 时参数丢失
错误代码:
javascript
// ❌ 错误:显式声明参数,导致额外参数丢失
XMLHttpRequest.prototype.open = function(method, url, async, user, password) {
return originalOpen.call(this, method, modifiedUrl, async, user, password);
}
解决方案:
javascript
// ✅ 正确:使用 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 文件本身也会被浏览器缓存
解决方案:使用动态脚本加载,添加时间戳参数
javascript
script.src = 'version.js?v=' + Date.now();
陷阱 4:执行顺序错误
问题原因:在 Docsify 加载后才重写请求方法
解决方案:在 Docsify 加载前重写请求方法
📊 效果验证
使用前
csharp
请求:README.md
状态:200 OK (from disk cache)
结果:显示旧内容
使用后
ini
请求:README.md?v=1.1.0_202603260852
状态:200 OK
结果:显示最新内容
🚀 实际应用指南
方案选择
| 项目规模 | 推荐方案 | 原因 |
|---|---|---|
| 小型项目 | 内联代码 | 简单直接,无需额外文件 |
| 中大型项目 | version.js | 集中管理,便于自动化 |
更新流程
场景 1:发布新版本
- 修改版本号(如 1.1.0 → 1.2.0)
- 重置时间戳
场景 2:同一版本内更新
- 保持版本号不变
- 更新时间戳(如 202603260852 → 202603261000)
自动化配置
创建 GitHub Actions 工作流,自动更新时间戳:
yaml
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 文件
- 总体评估:利大于弊,用户体验得到显著改善
📝 最佳实践总结
- 选择合适的方案:根据项目规模选择内联代码或 version.js
- 正确重写请求方法:使用 arguments 传递所有参数
- 同时支持两种请求方式:重写 XMLHttpRequest 和 fetch
- 处理 version.js 自身缓存:使用动态加载 + 时间戳
- 配置自动化更新:集成 GitHub Actions 自动更新时间戳
- 监控和测试:定期检查缓存效果,确保方案有效
🔧 代码优化建议
1. 错误处理
javascript
// 增强版:添加错误处理
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. 配置灵活性
javascript
// 增强版:可配置的文件扩展名
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 文档缓存问题有所帮助!如果你有任何问题或建议,欢迎在评论区留言。🎉