この記事の要点
この記事の重要ポイント
- 1
虽然 Astro Assets (`src/assets`) 是官方推荐方案,但对大规模存量站点来说迁移成本极高。
- 2
为解决构建时间延长和仓库异常肥大(超过 160MB)的问题,我们选择了更务实的 CLI 处理方式。
- 3
通过 Sharp 和 fast-glob 编写的脚本,在不改变文件名的前提下减少了 96MB(60%)的体积。
- 4
比起等待“完美方案”,持续积累“当下可行”的微小改进反而更有意义。
100MB 的红线
每个开发者都会在某个时刻撞上这道墙:当 public/ 文件夹变成技术债的瞬间。
Vercel 的部署变慢了,git clone 变得极其痛苦,Lighthouse 的“LCP(最大内容绘制)”指标也因为未压缩的大图而满江红。
本博客“GADGET LAB”也不例外。
250 多张图片,总容量高达 160MB。其中很多是随手粘贴的原始 PNG,或者是直接从相机里拷出来的巨型 JPEG。
“必须做点什么,但我没时间重写几百篇 MDX 里的路径。”
这就是一切的起点。
两条路:理想与现实
在 Astro 项目中,图片优化通常有两种解法。
A 计划:Astro Assets (src/assets) —— 理想国
自 Astro 5.0 以后,这无疑是黄金标准:
- 自动格式转换 :根据浏览器自动生成 AVIF 或 WebP。
- 响应式支持 :自动生成
srcset,确保手机端只加载小图。 - 防止布局抖动 :自动检测尺寸以规避 CLS。
但它有一个巨大的“坑”: 需要重写 Markdown 引用方式 。
// 之前 (引用 public 资源)

// 之后 (引用 src/assets 资源)
import myKeyboard from "../../assets/standing-desk-2026.jpg";
<Image src={myKeyboard} alt="My Keyboard" />
对于拥有几百篇文章的站点,迁移每一张图片的路径和导入语句,目前的成本实在是太高了。
B 计划:“Optimized Public” —— 现实主义
我选择了那条“路子野但管用”的路:维持 public 文件夹结构不变,仅仅优化其内部文件。
- 代码零改动 :现有的博文不需要修改一行代码。
- 即时生效 :只要跑一次脚本,整个站点瞬间“瘦身”。
- 唯一遗憾 :无法像官方方案那样自动生成多重分辨率(
srcset)。
我决定不让“完美的方案”阻碍“更好的现状”,先用 CLI 压缩顶上去。
落地:基于 Sharp 的批量压缩
以前我们常用 imagemin,但该库目前维护较慢。
这次我编写了一个定制脚本,结合了 Node.js 下处理速度最快的 Sharp 库和高速文件搜索库 fast-glob 。
脚本全貌
这就是用于“救急”的 scripts/optimize_images.ts:
import fs from "fs";
import path from "path";
import sharp from "sharp";
import fg from "fast-glob";
import SummarySlides from "@/components/ui/SummarySlides";
const PUBLIC_DIR = path.join(process.cwd(), "public/images");
const QUALITY = 80; // 肉眼几乎看不出区别的阈值
async function optimizeImages() {
// 使用 fast-glob 进行极速扫描
const files = await fg(["**/*.{jpg,jpeg,png}"], {
cwd: PUBLIC_DIR,
absolute: true,
});
for (const file of files) {
const originalSize = fs.statSync(file).size;
const ext = path.extname(file).toLowerCase();
let pipeline = sharp(file);
// 内部压缩,保留原始后缀
if (ext === ".jpg" || ext === ".jpeg") {
pipeline = pipeline.jpeg({ quality: QUALITY, mozjpeg: true });
} else if (ext === ".png") {
pipeline = pipeline.png({
quality: QUALITY,
compressionLevel: 9,
palette: true,
});
}
const buffer = await pipeline.toBuffer();
// 仅在体积确实缩小的情况下才覆盖(防止二次压缩导致变大)
if (buffer.length < originalSize) {
fs.writeFileSync(file, buffer);
console.log(`✅ 已优化: "${path.basename(file)}"`);
}
}
}
optimizeImages();
mozjpeg: true 和 palette: true (对于 PNG)
是大幅减小体积的关键。开启这两个参数可以在几乎不损失画质的情况下,实现质的飞跃。
战果:惊人的 96MB 降幅
脚本运行的瞬间,终端像瀑布一样刷屏,整个过程仅耗时 5 秒。
| 指标 | 压缩前 | 压缩后 | 优化率 |
|---|---|---|---|
| 总容量 | 160.5 MB | 64.3 MB | **-60%** |
| 文件数量 | 255 张 | 255 张 | 无变化 |
| 构建速度 | 较慢 | 明显提升 | - |
| 代码改动 | - | 无 | 零改动 |
从肉眼来看,压缩前后的图片几乎无法分辨。但从 Vercel 的流量账单和加载性能来看,世界已然不同。
总结:先“止血”,再治根
作为工程师,我们往往会有所谓的“技术洁癖”,固执于最新的最佳实践。 但如果迁移成本导致改进被无限期推迟,那其实是在本末倒置。
先通过脚本优化 public 文件夹来“止血”,至于完美的架构迁移,可以等以后有闲暇时再慢慢折腾。
如果你的项目中也积攒了大量笨重的图片,不妨试试这个方案。 5 秒钟之后,你会感谢现在的自己。






⚠️ コメントのルール
※違反コメントはAIおよび管理者により予告なく削除されます
まだコメントがありません。最初のコメントを投稿しましょう!