Ghost 博客 Cloudflare 缓存优化完整指南:自动清除缓存 + CDN 加速配置
一、前言
因为博客服务器在美西,大陆用户访问时需要跨太平洋访问。虽然博客启用了 Cloudflare 的 CDN,但众所周知,减速 CDN 只会让你看到红红的一片。
1.1 物理现实
Cloudflare 的 CDN 在默认状态下只会缓存静态的内容,例如 .jpg/.png/.css/.js/.woff 等文件。其他文件,例如网页 html等动态文件都是不缓存的。
亚洲用户访问,都需要 Cloudflare 访问博客在美西的源服务器,才能把网页html等动态文件转发给用户。而这个过程,需要跨越太平洋,光物理距离所带来的延迟就有200-500 ms。
所以,为了加速大陆用户访问,我们便可以利用 Cloudflare 提供的缓存规则,来自定义缓存逻辑,从而实现全站缓存。这样大陆用户访问,由于开启了全站缓存,Cloudflare 会优先从最近节点(香港/新加坡)提供内容,无需访问美西源服务器,从而大大提升网站访问速度。
1.2 方案优点
- 全站缓存:利用 Cloudflare 缓存静态资源 (CSS/JS/IMG) 和 HTML 页面
- 就近访问:大陆用户直接命中香港/新加坡边缘节点,无需回源
- 自动刷新:发布、编辑或删除文章时,自动清除对应 URL 的缓存
- 后台保护:确保 Ghost Admin 管理后台不受缓存干扰,管理操作无缝衔接
1.3 最终效果

1.4 架构概览
自定义缓存>>降低加载时间
利用 Cloudflare 的自定义缓存规则,将 HTML 等动态内容也缓存到边缘节点。用户访问时直接命中缓存,无需回源,大幅缩短加载时间

Webhook + Worker 自动清除缓存
问题:开启全站缓存后,在 Ghost 后台更新内容,用户访问时看到的仍是旧版——因为 Cloudflare 边缘节点会直接返回之前缓存的页面,而不会主动去源站获取最新内容。
传统做法是每次更新后手动去 Cloudflare 后台清缓存:
Caching → Configuration → Purge Cache → Purge Everything
解决方案:利用 Ghost 的 Webhook 功能,配合 Cloudflare Worker 实现全自动化:
Ghost 内容变动 → 触发 Webhook → Cloudflare Worker → 调用 Purge API → 清除旧缓存
这样,每次发布、编辑或删除文章,缓存都会自动刷新,无需手动操作。

二、Cloudflare 缓存规则配置
进入 Cloudflare Dashboard → Rules → Cache Rules,按顺序配置以下三条规则。
注意:规则的顺序非常重要!请确保优先级顺序正确。
2.1 规则 1: Ghost Bypass(优先级最高)
作用:排除 Ghost 后台、API 和预览链接,防止管理员无法登录或看到旧数据。
- Rule Name: Ghost Bypass
- Expression(表达式):
(http.cookie contains "ghost-members-ssr") or
(http.cookie contains "ghost-admin-api-session") or
(http.request.uri.path contains "/ghost/") or
(http.request.uri.path contains "/p/") or
(http.request.uri.path contains "/members/")- Cache Status: Bypass cache
2.2 规则 2: Caching All Statics
作用:强力缓存 CSS、JS、字体和图片资源。
- Rule Name: Caching All Statics
- Expression(表达式):
(http.host eq "www.yourblog.com") and (
(http.request.uri.path contains "/content/") or
(http.request.uri.path contains "/assets/") or
(http.request.uri.path contains "/public/") or
(http.request.uri contains ".js") or
(http.request.uri contains ".css") or
(http.request.uri contains ".jpg") or
(http.request.uri contains ".jpeg") or
(http.request.uri contains ".png") or
(http.request.uri contains ".webp") or
(http.request.uri contains ".gif") or
(http.request.uri contains ".svg") or
(http.request.uri contains ".ico") or
(http.request.uri contains ".woff") or
(http.request.uri contains ".woff2")
)注意,替换 yourblog.com 成你自己的博客网址
2.3 规则 3: Ghost Cache HTML(优先级最低)
作用:缓存前台所有 HTML 页面。
- Rule Name: Ghost Cache HTML
- Expression(表达式):
(http.host eq "www.yourblog.com" and not http.request.uri.path contains "/ghost/" and not http.request.uri.path contains "/members/" and not http.request.uri.path contains "/p/" and not http.cookie contains "ghost-members-ssr" and not http.cookie contains "ghost-admin-api-session")注意,替换 yourblog.com 成你自己的博客网址
- Cache Status: Eligible for cache
- Edge TTL: 2 hours (或更长,反正有自动清除)
三、配置 Cloudflare Worker
3.1 Cloudflare Worker 代码
在 Workers & Pages 中创建一个新 Worker,命名为 ghost-cache-purge。将以下代码覆盖到 worker.js 中:
export default {
async fetch(request, env, ctx) {
// 只接受 POST 请求
if (request.method !== 'POST') {
return new Response('Method not allowed', { status: 405 });
}
try {
// 解析 Ghost 发送的 Webhook 数据
const payload = await request.json();
console.log('=== Ghost Webhook Received ===');
console.log('Timestamp:', new Date().toISOString());
console.log('Payload:', JSON.stringify(payload, null, 2));
// 需要清除缓存的 URL 列表
const urlsToPurge = [];
const siteUrl = env.SITE_URL || 'https://www.yourblog.com';
console.log('Using SITE_URL:', siteUrl);
console.log('CLOUDFLARE_ZONE_ID configured:', !!env.CLOUDFLARE_ZONE_ID);
console.log('CLOUDFLARE_API_TOKEN configured:', !!env.CLOUDFLARE_API_TOKEN);
// 备用域名(www 和非 www 切换)
const altSiteUrl = siteUrl.includes('www.')
? siteUrl.replace('www.', '')
: siteUrl.replace('://', '://www.');
// 辅助函数:添加 URL 及其备用版本
const addUrl = (url) => {
if (!url) return;
const fullUrl = url.startsWith('http') ? url : siteUrl + url;
urlsToPurge.push(fullUrl);
// 添加 alt 版本
urlsToPurge.push(fullUrl.includes('www.')
? fullUrl.replace('www.', '')
: fullUrl.replace('://', '://www.'));
};
// 辅助函数:添加带 slug 的 URL
const addSlugUrl = (prefix, slug) => {
if (!slug) return;
urlsToPurge.push(`${siteUrl}${prefix}${slug}/`);
urlsToPurge.push(`${altSiteUrl}${prefix}${slug}/`);
};
// 标记:是否有 tag 相关变化
let hasTagChanges = false;
// 标记:是否有 author 相关变化
let hasAuthorChanges = false;
// ========================================
// 1. 始终清除首页(www 和非 www 版本)
// ========================================
urlsToPurge.push(siteUrl);
urlsToPurge.push(siteUrl + '/');
urlsToPurge.push(altSiteUrl);
urlsToPurge.push(altSiteUrl + '/');
// 添加分页页面(首页分页)
for (let i = 2; i <= 5; i++) {
urlsToPurge.push(`${siteUrl}/page/${i}/`);
urlsToPurge.push(`${altSiteUrl}/page/${i}/`);
}
// ========================================
// 2. 处理 Post(文章)事件
// ========================================
const post = payload?.post?.current || payload?.post || null;
if (post) {
// 添加文章 URL
addUrl(post.url);
// 用 slug 构建 URL(备用)
addSlugUrl('/', post.slug);
// 清除文章关联的 Tag 页面
if (post.tags && Array.isArray(post.tags) && post.tags.length > 0) {
hasTagChanges = true;
for (const tag of post.tags) {
addSlugUrl('/tag/', tag.slug);
// 添加 tag 分页
for (let i = 2; i <= 3; i++) {
urlsToPurge.push(`${siteUrl}/tag/${tag.slug}/page/${i}/`);
}
}
}
// 清除文章的 primary_tag 页面
if (post.primary_tag?.slug) {
hasTagChanges = true;
addSlugUrl('/tag/', post.primary_tag.slug);
}
// 清除文章关联的 Author 页面
if (post.authors && Array.isArray(post.authors) && post.authors.length > 0) {
hasAuthorChanges = true;
for (const author of post.authors) {
addSlugUrl('/author/', author.slug);
}
}
// 清除文章的 primary_author 页面
if (post.primary_author?.slug) {
hasAuthorChanges = true;
addSlugUrl('/author/', post.primary_author.slug);
}
}
// ========================================
// 3. 处理 Page(页面)事件
// ========================================
const page = payload?.page?.current || payload?.page || null;
if (page) {
addUrl(page.url);
addSlugUrl('/', page.slug);
}
// ========================================
// 4. 处理 Tag 事件(当 Tag 本身被修改时)
// ========================================
const tag = payload?.tag?.current || payload?.tag || null;
if (tag && tag.slug) {
hasTagChanges = true;
addSlugUrl('/tag/', tag.slug);
// 添加 tag 分页
for (let i = 2; i <= 3; i++) {
urlsToPurge.push(`${siteUrl}/tag/${tag.slug}/page/${i}/`);
}
}
// ========================================
// 5.新增:清除 /tags/ 列表页
// ========================================
if (hasTagChanges) {
urlsToPurge.push(`${siteUrl}/tags/`);
urlsToPurge.push(`${altSiteUrl}/tags/`);
console.log('Tag changes detected - purging /tags/ list page');
}
// ========================================
// 6.新增:清除 /authors/ 列表页
// ========================================
if (hasAuthorChanges) {
urlsToPurge.push(`${siteUrl}/authors/`);
urlsToPurge.push(`${altSiteUrl}/authors/`);
console.log('Author changes detected - purging /authors/ list page');
}
// ========================================
// 7. 去重
// ========================================
const uniqueUrls = [...new Set(urlsToPurge)];
console.log('=== Purging URLs ===');
console.log('Total URLs to purge:', uniqueUrls.length);
console.log('URLs:', JSON.stringify(uniqueUrls, null, 2));
// 检查环境变量
if (!env.CLOUDFLARE_ZONE_ID || !env.CLOUDFLARE_API_TOKEN) {
console.error('Missing required environment variables!');
console.error('CLOUDFLARE_ZONE_ID:', env.CLOUDFLARE_ZONE_ID ? 'SET' : 'MISSING');
console.error('CLOUDFLARE_API_TOKEN:', env.CLOUDFLARE_API_TOKEN ? 'SET' : 'MISSING');
return new Response(JSON.stringify({
success: false,
error: 'Missing required environment variables'
}), {
status: 500,
headers: { 'Content-Type': 'application/json' }
});
}
// 调用 Cloudflare API 清除缓存
const purgeUrl = `https://api.cloudflare.com/client/v4/zones/${env.CLOUDFLARE_ZONE_ID}/purge_cache`;
console.log('Calling Cloudflare API:', purgeUrl);
const purgeResponse = await fetch(purgeUrl, {
method: 'POST',
headers: {
'Authorization': `Bearer ${env.CLOUDFLARE_API_TOKEN}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
files: uniqueUrls
}),
});
console.log('Cloudflare API response status:', purgeResponse.status);
const purgeResult = await purgeResponse.json();
console.log('Cloudflare API response:', JSON.stringify(purgeResult, null, 2));
if (purgeResult.success) {
const result = {
success: true,
message: 'Cache purged successfully',
purgedUrls: uniqueUrls,
urlCount: uniqueUrls.length,
postTitle: post?.title || page?.title || 'N/A',
tags: post?.tags?.map(t => t.slug) || [],
hasTagChanges,
hasAuthorChanges,
cloudflareResponse: purgeResult
};
console.log('=== Purge Successful ===');
console.log('Result:', JSON.stringify(result, null, 2));
return new Response(JSON.stringify(result), {
status: 200,
headers: { 'Content-Type': 'application/json' }
});
} else {
console.error('=== Purge Failed ===');
console.error('Errors:', JSON.stringify(purgeResult.errors, null, 2));
return new Response(JSON.stringify({
success: false,
error: purgeResult.errors,
purgedUrls: uniqueUrls,
fullResponse: purgeResult
}), {
status: 500,
headers: { 'Content-Type': 'application/json' }
});
}
} catch (error) {
console.error('Worker error:', error);
return new Response(JSON.stringify({
success: false,
error: error.message
}), {
status: 500,
headers: { 'Content-Type': 'application/json' }
});
}
},
};这个 Cloudflare Worker 接收来自 Ghost 的 Webhook,自动清除相关页面的缓存,包括:
- 首页及分页
- 被修改的文章(Post)、页面(Page)
- 相关的标签页面(Tag)、标签列表页(Tags)—— 前提主题有标签列表页
- 相关的作者页面(Author)、作者列表页(Authors)——前提主题有作者列表页
3.2 配置 Cloudflare Worker 环境变量
在 Worker 的 Settings → Variables 中添加以下三个关键变量:
| 变量名 | 示例值 | 说明 |
|---|---|---|
CLOUDFLARE_ZONE_ID |
e2d8... |
Cloudflare 域名概览页右下角的 Zone ID |
CLOUDFLARE_API_TOKEN |
v5x9... |
需创建具有 Zone - Cache Purge 权限的 Token |
SITE_URL |
https://www.yourblog.com |
你的完整博客地址 (注意:不要带尾部斜杠) |
创建 API Token
登录 cloudflare 后台,右上角点击头像,选择 My Profile。选择 API Tokens,点击 Create Token(创建 Token)

选择 Create Custom Token

Token Name: Ghost Cache Purge
Permissions:Zone >> Cache Purge >> Purge
Zone Resources: Include >> Specific zone >> yourblog.com(注意选择你自己的博客网址)

四、Ghost Webhook 对接
4.1 Ghost后台设置
1.进入 Ghost Admin → Settings → Integrations。

2.点击 Add custom integration,命名为 "Cloudflare Cache"。

3.按需添加各个事件的 Webhook,同时 Target URL 均填入你的 Worker 地址(如 https://ghost-cache-purge.yourname.workers.dev):

4.2 Webhook 事件清单
建议配置以下所有事件,以确保缓存清理的覆盖面最全:
| Event (事件类型) | 说明 (触发场景) | Target URL (填入 Worker 地址) |
|---|---|---|
| Post published | 文章发布 (新文章上线时) | https://your-worker.workers.dev |
| Post updated | 文章更新 (修改错别字、更新内容时) | https://your-worker.workers.dev |
| Post deleted | 文章删除 (文章直接删除) | https://your-worker.workers.dev |
| Published post uppdated | 已发布文章更新 (已发布文章修改错别字,更新内容) | https://your-worker.workers.dev |
Post unpublished | 文章下架 (文章下架时) | https://your-worker.workers.dev |
| Page published | 页面发布 (关于、友链等独立页面上线) | https://your-worker.workers.dev |
| Page updated | 页面更新 (独立页面内容变动) | https://your-worker.workers.dev |
| Page deleted | 页面删除 (独立页面下架) | https://your-worker.workers.dev |
| Tag updated | 标签更新 (修改标签名或描述时) | https://your-worker.workers.dev |
| Tag added to post | 文章添加标签 (文章添加标签时) | https://your-worker.workers.dev |
| Tag removed from post | 删除文章标签 (文章的标签被删除时) | https://your-worker.workers.dev |
| Site changed | 站点变更 (全局保底,任何变动都触发) | https://your-worker.workers.dev |
虽然Post updated通常能涵盖标签变动,但为了保险起见,建议加上Tag updated。Site changed是最后一道防线,虽然它的 Payload 为空,但 Worker 代码已经处理了这种情况(会默认清除首页),确保万无一失。
按照我的理解,post updated 应该是草稿箱中的帖子更新。草稿箱的内容根本未发布,自然也不会有缓存,所以真正有效的是 published post updated (已发布文章更新)。
但是我在管理后台却看到了 post updated 事件被触发了。虽然有可能不起效,但保险起见,还是添加上比较好。
如果更新/切换主题,请直接清除所有缓存。
五、验证测试
配置完成后,我们来验证一下效果。
5.1 检查 Headers
打开博客首页或文章页,使用开发者工具 (F12) 查看 Network 面板,点击文档请求,查看 Headers:
- 第一次访问:cf-cache-status: MISS (正常,正在回源缓存)
- 第二次访问:cf-cache-status: HIT (成功!已命中缓存)

5.2 检查自动清除
- 在 Ghost 后台修改一篇已发布的文章(例如修改一个标点符号)。
- 点击 Update。
- 立即在无痕模式下访问该文章。
- 如果你看到修改后的内容,说明缓存清除成功!
- (可选) 查看 Worker 后台的 Logs,确认是否有 "Purging URLs" 的日志。
参考链接
- [Cloudflare Cache Rules 文档]
(https://developers.cloudflare.com/cache/how-to/cache-rules/)
- [Cloudflare Purge Cache API
(https://developers.cloudflare.com/api/operations/zone-purge)
- [Ghost Webhooks 文档]
(https://ghost.org/docs/webhooks/)