折腾博客图床:从静态资源仓库到 PicGo 上传流程
之前我博客里的图片基本都丢在 Jerry-FaGe/jerry-fage-static 这个 GitHub 仓库里,然后通过 jsDelivr 直接引用。刚开始图片少,目录随便放放也没啥问题,反正能显示就行。
直到后来我看了一眼仓库结构:img/posts 下面一堆 pasted-1.png、生成API Key.png、迫害1.png,好家伙,
所以这次干脆趁文章和图片还没多到不可收拾,把图床仓库、博客引用、PicGo 上传流程一起捋了一遍。本文就当是这次折腾的复盘,防止以后我又忘了自己为啥这么设计。
本文不是“从零教你搭图床”的教程,而是一次真实的博客资源治理现场。关键词:GitHub 静态资源仓库、jsDelivr、Hexo、Butterfly、PicGo、以及一些该死的缓存。
一开始的问题
原来的仓库结构大概长这样:
1 | jerry-fage-static/ |
不能说完全没设计,只能说充满了随缘人生。
主要问题有几个:
文章图片全堆在 img/posts,时间一长根本不知道哪张图属于哪篇文章
文件名中英文混着来,有些 URL 被编码后看着像上古咒语
默认封面、关于页资源、文章资源虽然有分类,但层级不够清晰
这个仓库以后可能不只存博客资源,根目录直接放 img、js 不太优雅
尤其是 pasted-1.png 这种文件名,过几个月再看,谁知道它是啥?除非点开图片看一眼。问题是图一多,点开也烦。
先定新结构
我一开始的想法是直接把旧结构删了,全部迁移到新目录。但很快意识到这事不能这么莽。
旧链接还在博客里被引用,如果资源仓库先删旧路径,博客页面就会先炸。正确顺序应该是:先新增新结构并保留旧结构,替换博客引用并验证无误后,再考虑删旧路径。
最后定下来的新结构是这样:
1 | jerry-fage-static/ |
几个设计点:
博客专用资源统一放在 blog/ 下面。这样这个仓库以后如果还想放别的项目资源,不会跟博客图片混在一起。
1 | blog/img/posts/ |
shared/ 用来放多个项目都可能复用的通用资源,比如头像、通用图标、占位图之类的。
目前暂时只有 README,先把坑位占上。
旧的 img/、js/ 不删,先保留。等博客引用全部替换完、构建验证没问题、线上也稳定一段时间后,再考虑清理。
这叫稳,不叫怂。
历史图片怎么归档
旧文章图片不多,所以直接按文章拆了目录。
1 | blog/img/posts/chatgpt-api-first-look/ |
默认封面也顺手从:
1 | img/cover/default_cover_1.webp |
改成:
1 | blog/img/covers/default/default-cover-001.webp |
这样排序和肉眼识别都舒服不少。
关于页的技能图标也收进了 about 下面,因为它本来就是关于页的一部分:
1 | blog/img/pages/about/skills/linux.png |
替换博客里的引用
资源仓库整理完之后,下一步就是改博客源码里的 URL。
比如旧引用:
1 | https://cdn.jsdelivr.net/gh/Jerry-FaGe/jerry-fage-static@master/img/posts/pasted-1.png |
替换成:
1 | https://cdn.jsdelivr.net/gh/Jerry-FaGe/jerry-fage-static@master/blog/img/posts/sign-in-with-apple-python-code-verify/01-find-identifier.png |
涉及到的文件主要是这些:
1 | _config.butterfly.yml |
替换规则简单记一下
1 | img/cover/default_cover_N.webp |
替完之后跑了一遍:
1 | hexo clean |
构建没炸,抽样访问新图片 URL 也都是 200。这一步很重要,不然你以为自己整理完了,结果一上线全是裂图,那就成赛博装修事故了。
要不要顺手换 CDN?
本来还想趁这次改路径,顺便看看有没有更适合国内访问的 CDN。测了一圈之后结论是:
测过这些:
1 | cdn.jsdelivr.net |
本机测试里 raw.githubusercontent.com 甚至还挺快,但这玩意儿在国内网络环境下懂的都懂,不能拿本机结果当生产结论。
后面用中国探针测了一下,cdn.jsdelivr.net 整体可用性还是最稳的。fastly.jsdelivr.net 和 gcore.jsdelivr.net 不是不能用,但没明显优势;raw.githubusercontent.com 和一些镜像域名更不适合当长期正式链接。
所以最后决定不换域名,只换路径:
1 | https://cdn.jsdelivr.net/gh/Jerry-FaGe/jerry-fage-static@master/blog/img/... |
如果以后真想再提升国内访问速度,那就不是换个 GitHub 镜像域名能解决的了,应该考虑:
腾讯云 COS + CDN
阿里云 OSS + CDN
七牛云
又拍云
这就是另一套成本了,先不折腾,毕竟博客不是视频网站。
PicGo 也顺手研究了一下
图床仓库整理完,下一个问题就来了:以后图片怎么上传才不把新结构再次搞成垃圾堆?
PicGo 的 GitHub 图床配置里有个 path,比如我现在可以固定写成:
1 | blog/img/posts/ |
然后上传图片。问题在于,PicGo 默认不知道“我现在正在写哪篇文章”。也就是说,它不会自动帮我把图片塞进:
1 | blog/img/posts/<post-slug>/ |
除非我借助插件,或者每篇文章建一份配置。每篇文章建配置就算了,太蠢了,写几篇文章之后配置列表能比文章还长。
于是我去翻了一圈 PicGo 插件。
picgo-plugin-folder-name 看起来很符合需求,本地文件夹叫啥,它就给你加什么目录前缀。
但是它有个比较坑的问题:单张上传可以,多张上传会失效。它的 issue 里就有人提过“单次上传多个文件时,插件不起作用”。
我还顺手看了下源码,里面有个循环条件大概长这样:
1 | for (let i = ctx.output.length - 1; i === 0; i--) { |
这个条件一看就不太对。多张图的时候 i 一开始就不是 0,循环直接不执行。好家伙,怪不得。
picgo-plugin-rename-file 更靠谱一点,它支持用变量重命名,比如:
1 | {localFolder:1}/{origin} |
如果我本地目录严格按文章 slug 来放,它确实能自动生成类似 post-slug/01-demo.png 的路径。
但这又引出另一个问题:我并不想为了上传图片,长期在博客源码里维护一套完整图片目录。
PicGo-Server 能提供 HTTP 上传接口,但它本质上还是走 PicGo 当前图床配置,不能很自然地在每次上传时动态覆盖 GitHub 的 path。
当然可以继续写脚本绕,但那就从“整理图床”变成“开发一个图床系统”了,没必要。
最后我还是选择一个更朴素但稳定的流程:
最终决定的上传流程
PicGo 的 GitHub 图床固定配置:
1 | 仓库名:Jerry-FaGe/jerry-fage-static |
写文章时,如果需要本地预览,就先放到:
1 | source/post-assets/<post-slug>/ |
比如:
1 | source/post-assets/blog-static-assets-picgo/cover.webp |
文章里临时这么写:
1 |  |
等文章写完,图片也确定不改了,再用 PicGo 上传。上传前在 PicGo 里把文件名改成:
1 | blog-static-assets-picgo/cover.webp |
因为 PicGo 的基础路径已经是 blog/img/posts/,所以最终远端路径就会变成:
1 | blog/img/posts/blog-static-assets-picgo/cover.webp |
文章里的图片再替换成:
1 |  |
这个流程不花哨,但优点是可控。目录是谁、文件叫什么、最终 URL 是什么,上传前就能看明白,不依赖插件猜我的意图。
为什么不用 post_asset_folder
Hexo 其实已经开了 post_asset_folder: true。如果文章叫:
1 | 我的文章.md |
那对应资源文件夹可以是:
1 | 我的文章/ |
然后 cover: cover.webp 这种写法,Butterfly 也能处理成文章路径下的图片。这个能力本身没问题,但对我现在的需求不太合适。
原因很简单:
本地图片只是写文章时临时预览用,最终还是应该上传到静态资源仓库,然后文章里保留 CDN URL。这样博客源码仓库更干净,资源仓库也能统一管理图片。
所以 source/post-assets/ 对我来说只是一个临时工作区,后续可以直接加到 .gitignore,不用跟着文章一起提交。
还有一个缓存坑
GitHub + jsDelivr 这个组合用起来很爽,但缓存要记住一点:
比如已经上线了:
1 | blog/img/posts/blog-static-assets-picgo/cover.webp |
后面发现图做错了,最好不要直接覆盖这个文件,而是上传一个新文件:
1 | blog/img/posts/blog-static-assets-picgo/cover-v2.webp |
然后改文章引用。
@master 分支路径虽然方便,但 CDN 缓存不是你想刷新就马上刷新。对已经发布的图片,换文件名比强行等缓存刷新靠谱得多。
我现在的命名习惯大概是这样:
1 | cover.webp |
不搞随机字符串,主要是随机名看着难受。文件名只要能表达图片内容,再加上必要的 v2、v3,基本就够用了。
这次实际做了什么
本次折腾记录
整理静态资源仓库
新增 blog/ 和 shared/ 命名空间,把博客图片、页面资源、默认封面重新归档。旧 img/、js/ 暂时保留,避免线上旧链接直接失效。
写中文 README
把资源仓库 README 改成中文,并且给 blog/、shared/ 各补了说明。以后再打开这个仓库,不至于靠记忆猜目录用途。
替换博客引用
把 Butterfly 配置、关于页数据、创意页数据、历史文章里的旧图床 URL 替换到新路径。
构建验证
跑 hexo clean 和 hexo server,同时抽样访问新 jsDelivr URL,确认新路径能正常返回 200。
研究 PicGo 流程
看了一圈 PicGo 插件,最后放弃“自动猜目录”的幻想,选择上传前手动重命名完整相对路径。
最后总结
这次折腾完之后,我现在的原则就很简单:
博客资源统一放 blog/,不要污染仓库根目录
单篇文章图片放 blog/img/posts/<post-slug>/
默认封面放 blog/img/covers/default/
页面专用资源放 blog/img/pages/<page-name>/
旧路径先保留,确认稳定后再清理
CDN 继续用 cdn.jsdelivr.net,暂时不换
PicGo 上传前手动重命名,不依赖有坑的自动目录插件
发布后的图片不要覆盖同名文件,改图就上 -v2
总之,图床这玩意儿刚开始随便放确实省事,但越往后越像家里那个“先随手放一下”的抽屉。现在整理一下不一定能一劳永逸,但至少下次我再上传图片的时候,不用面对一堆 pasted-1.png 开始怀疑人生。




