博客图片压缩优化技术报告:文章加速加载、画廊保留原图
曾为文章正文图片引入构建期 WebP 压缩(2MB → 63KB)。该方案已于 2026-08-02 下线,图片迁移至独立图床仓库 + jsDelivr CDN;本文保留作历史技术记录,新方案见文末附录。
博客图片压缩优化技术报告
⚠️ 方案已废弃(2026-08-02):本文描述的构建期压缩插件已随图片迁移下线——图片现由独立图床仓库
AnAcretiondisk9986/blog-images+ jsDelivr CDN 托管,文章/画廊直接引用外链,不再需要构建期压缩。本文保留作历史技术记录与踩坑参考,现役方案见文末附录。
需求
站点图片全部位于 public/image/,文章正文直接以 Markdown 引用原图。部分截图高达 2–6MB,文章页加载明显变慢。目标是:
- 文章页图片做「适当压缩」,加快加载;
- 画廊页(
/gallery/)无论卡片还是大图预览,都不希望失去原图质量——画廊是图像档案,画质优先。
后经讨论进一步明确:画廊卡片缩略图也采用压缩版(滚动时快速呈现),点击查看大图时才加载原图。
方案概览
核心思路:构建期压缩 + 双钩子协作,不引入运行时处理,不改动文章 Markdown 内容。
- Sätteri hast 插件(Markdown 渲染时):把文章正文里的
/image/xxx压缩为 WebP,src替换为压缩版; - Astro integration(
astro:build:done):构建收尾时把压缩产物复制进dist; - 画廊页:通过
optimizeImageUrl()为卡片取压缩版 URL,data-gallery-src仍保留原图供查看大图。
压缩参数:WebP quality: 78,宽度超过 1920px 才等比缩小(文章容器宽度远小于此,视觉无差别)。
public/image/xxx.png 原图(入库)
public/image/opt/<hash>-xxx.webp 构建产物(gitignore,不入库)
dist/image/opt/<hash>-xxx.webp 部署产物
技术难点与解决
1. Astro 7 默认换用 Sätteri Markdown 管道
Astro 7 的默认 Markdown 处理器从 unified(remark/rehype)换成 Sätteri。传统的:
markdown: { rehypePlugins: [myPlugin] }
直接报错,要求安装 legacy 包 @astrojs/markdown-remark。新写法:
import { satteri } from '@astrojs/markdown-satteri';
export default defineConfig({
markdown: {
processor: satteri({
hastPlugins: [myHastPlugin],
}),
},
});
插件 API 也完全不同:不再是 unified 的 tree 遍历,而是按节点类型过滤的 visitor:
const plugin = {
name: 'astro-optimize-images',
element: {
filter: ['img'],
async visit(node, ctx) {
// 只处理 <img>;ctx.setProperty() 原地改属性
},
},
};
2. 渲染阶段写 dist/ 会被清掉
最初尝试在 hast 插件里直接写 dist/image/opt/,构建后文件全部丢失——Astro 的构建流程中,路由渲染之后还有步骤会清理/重建 dist。渲染阶段写入 dist 不可靠。
解决:插件只把产物写进源目录 public/image/opt/(它会被 vite 复制进 dist,且渲染缓存命中时旧产物依然在),再由 astro:build:done 钩子在构建完全结束后统一复制到 dist/image/opt/。
3. astro.config 与渲染管道是两份独立模块
astro.config.mjs 被 esbuild 打包成独立的 config 模块图,与 vite 渲染管道互不共享内存。模块级变量(如产物清单 Map)在两者之间不通——integration 读到的永远是空。
解决:不依赖内存状态,以文件系统为通道。插件写 public/image/opt/,integration 直接扫描该目录复制,天然解耦。
4. 渲染缓存:插件「根本没执行」也能出对的结果
最隐蔽的一个坑。Astro 7 会缓存 Markdown 编译结果:配置不变时,二次构建直接复用缓存的 HTML,hast 插件不会运行。此前多次「看起来成功」的构建,其实是首次运行留下的缓存。
这解释了为什么产物目录始终为空,但 HTML 里 src 却已被替换成 .webp。
解决:产物放源目录 public/image/opt/(缓存命中时旧文件仍在),astro:build:done 无条件从源目录重建 dist/image/opt/,与「插件是否运行」彻底解耦。
实现细节
产物复用与失效
optimize() 对同一 src 有模块级缓存(防重复压缩),产物已存在且源图 mtime 未更新时直接复用;源图被替换后 mtime 变新,自动重新压缩。换图无需手动清缓存。
文件名防碰撞
输出文件名带目录 hash 前缀:
const dirHash = createHash('sha1').update(path.dirname(decoded)).digest('hex').slice(0, 8);
const outName = `${dirHash}-${fileBase}.webp`;
避免不同子目录下同名文件互相覆盖。
安全与容错
- 路径穿越防护:解码后的路径必须仍位于
public/内; - 仅处理
/image/前缀,其余路径直接跳过; - 单张压缩失败:
console.warn+ 回退原图,不中断构建; - dev 模式完全不压缩,行为与改动前一致。
效果数据
| 原图 | 大小 | 压缩后 | 降幅 |
|---|---|---|---|
| 文章截图 PNG | 2,032,852 B | 63,088 B | ≈97% |
| 文章截图 PNG | 2,142,891 B | 92,284 B | ≈96% |
| 独立收藏 PNG | 5,073,386 B | 93,792 B | ≈98% |
文章页与画廊卡片加载的流量降为原来的 2–4%,画廊大图预览不受影响。
维护说明
本节指引随插件下线已失效,现役操作见文末附录「维护」小节。
- 以后写文章:图片照旧放进
public/image/、Markdown 里正常引用,压缩自动生效; - 新增独立收藏:只需写进
src/data/gallery.json,构建时自动生成卡片缩略图; - 想调压缩强度:改
src/plugins/optimize-images.mjs里的QUALITY/MAX_WIDTH; - 回滚:
git revert <commit>即可,压缩产物不入库、不留残留。
结语
这次优化最大的成本不在压缩本身(sharp 一行事),而在于搞清楚 Astro 7 的新架构:Sätteri 管道、构建时序、模块隔离与渲染缓存。最终用「源目录产物 + 收尾复制」的简单架构绕开了全部四个坑,文章与画廊各取所需——正文快、画廊全。
附录:2026-08-02 方案演进——独立图床仓库 + jsDelivr CDN(现役)
为什么演进
- 仓库膨胀:41MB 原图全部入库,
.git越来越大,影响 clone/备份,GitHub 也建议单仓库控制在 1GB 内; - 境内访问慢:
github.io与raw.githubusercontent.com在国内直连速度差,图片加载是页面体验的最大瓶颈; - 机制冗余:构建期压缩 + 画廊双层图(压缩图/原图)在图片整体变小后失去意义。
新架构
- 独立公开图片仓库
AnAcretiondisk9986/blog-images:与博客仓库彻底分离,图片不再进入博客仓库; - jsDelivr CDN 外链:
https://cdn.jsdelivr.net/gh/AnAcretiondisk9986/blog-images@main/image/<name>.webp——有国内节点,境内访问快,首次访问后全站 CDN 缓存; - 一次性批量压缩:迁移时用 sharp 把 31 张原图统一转 WebP(参数沿用 quality 78、超 1920px 等比缩小),41MB → 2.9MB(平均 96KB/张);
- 管理面板自动上传:上传/URL 导入 → 自动转 WebP → 写入本地
../blog-images/image/→ git 推送图片仓库 → 返回 jsDelivr 外链,写作流程不变(粘贴即用); - 画廊:预览与点开大图均为同一份 WebP(图小,点开直接命中浏览器缓存);独立收藏另有
image/original/原图归档(6 张,18MB),查看器内「加载原图」按钮可切换原图/压缩版,并按图片长宽比自适应窗口、支持缩放滑块与拖拽平移; - 构建期插件已删除:
src/plugins/optimize-images.mjs下线,astro.config.mjs不再注册,构建不再依赖 sharp(sharp 仅保留给管理面板上传转码)。
效果
- 博客仓库图片体积 41MB → 0(图片仓库约 21MB,含原图归档);
- 境内加载走 jsDelivr 国内节点,明显快于 github.io;
- 图片不再占用 Pages 带宽,博客仓库 clone 更快。
维护
- 写文章:管理面板粘贴图片即可,自动完成「压缩 → 推送图床 → 生成外链」;
- 手动加图:文件放进本地
../blog-images/image/,在该目录git push origin main(注意图片仓库 remote 是 SSH,HTTPS 直连 GitHub 会被重置); - 更新同名文件:jsDelivr 有缓存,需访问
https://purge.jsdelivr.net强制刷新; - 原图底片:独立收藏 6 张在
image/original/(从博客 git 历史恢复),其余原图仍留在博客仓库 git 历史中(9c99de8之前的 commit 可恢复)。