💡

この記事の要点

この記事の重要ポイント

  • 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 资源)
![image](/images/standing-desk-2026.jpg)

// 之后 (引用 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: truepalette: true (对于 PNG) 是大幅减小体积的关键。开启这两个参数可以在几乎不损失画质的情况下,实现质的飞跃。

战果:惊人的 96MB 降幅

脚本运行的瞬间,终端像瀑布一样刷屏,整个过程仅耗时 5 秒。

指标 压缩前 压缩后 优化率
总容量 160.5 MB 64.3 MB **-60%**
文件数量 255 张 255 张 无变化
构建速度 较慢 明显提升 -
代码改动 - 零改动

从肉眼来看,压缩前后的图片几乎无法分辨。但从 Vercel 的流量账单和加载性能来看,世界已然不同。

总结:先“止血”,再治根

作为工程师,我们往往会有所谓的“技术洁癖”,固执于最新的最佳实践。 但如果迁移成本导致改进被无限期推迟,那其实是在本末倒置。

先通过脚本优化 public 文件夹来“止血”,至于完美的架构迁移,可以等以后有闲暇时再慢慢折腾。

如果你的项目中也积攒了大量笨重的图片,不妨试试这个方案。 5 秒钟之后,你会感谢现在的自己。