100MBの壁
どの開発者も、ある時点でその壁にぶつかります。public/ フォルダが負債に変わる瞬間です。
Vercelのデプロイが遅くなり、git clone に時間がかかり、Lighthouseのスコアが「LCP (Largest Contentful Paint)」の警告で赤く染まる。
当ブログ「GADGET LAB」も例外ではありませんでした。
255枚以上の画像、総容量160MB。その多くは、執筆の勢いで貼り付けられた未圧縮のPNGや、カメラから取り出したままの巨大なJPEGでした。
「なんとかしなければならない。でも、全記事のMarkdownを書き換える時間はない」
これが、スタート地点でした。
2つの道:理想と現実
Astroプロジェクトにおいて、画像最適化には主に2つのアプローチがあります。
Path A: Astro Assets (src/assets) —— 理想郷
Astro 5.0以降、これは疑いようのないゴールデンスタンダードです。
- 自動フォーマット変換 : ブラウザに合わせてAVIFやWebPを自動生成。
- レスポンシブ対応 :
srcsetを自動生成し、スマホには小さな画像を配信。 - レイアウトシフト防止 : 画像サイズを自動検知し、CLSを防ぐ。
しかし、ここには大きな「キャッチ」があります。 Markdownの書き換え です。
// Before (public)

// After (src/assets)
import myKeyboard from "../../assets/standing-desk-2026.jpg";
<Image src={myKeyboard} alt="My Keyboard />
250枚以上の画像、数百の記事。すべてのパスを書き換え、ファイルを移動させるコストは、今の段階では高すぎました。
Path B: “Optimized Public” —— 現実解
私が選んだのは、「public フォルダのまま、中身だけを最適化する」というプラグマティックな道です。
- コード変更ゼロ : 既存の記事(MDX)は1行も触る必要がありません。
- 即効性 : スクリプトを1回走らせるだけで、サイト全体が軽くなります。
- 欠点 : Astro Assetsのような高度なレスポンシブ対応(
srcset)は諦める必要があります。
「完璧(Astro Assets)」を敵にして、何もしないよりは、「良(CLI圧縮)」を選んで前に進むことにしました。
現状把握
public/images フォルダの容量とファイル形式をスキャン。
戦略決定
移行コストと効果を天秤にかけ、CLIアプローチを選択。
自動化の実装
Sharpとfast-globを用いた一括圧縮スクリプトの作成。
効果検証
容量削減率とビジュアル品質を最終チェック。
実装: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";
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") {
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(`✅ Optimized: "${path.basename(file)"}`);
}
}
}
optimizeImages();
mozjpeg: true と palette: true (PNG)
が重要です。これらを有効にすることで、画質を維持したままファイルサイズを劇的に落とせます。
結果:96MBの衝撃
スクリプトを実行した瞬間、ターミナルには高速でログが流れ、わずか5秒で処理が完了しました。
| 項目 Before | After | 削減率 |
|---|---|---|
| 総容量, 160.5 MB, 64.3 MB, **-60%** | ||
| ファイル数, 255, 255, 変化なし | ||
| ビルド時間, 遅い, 改善, - | ||
| コード変更, -, なし, ゼロ |
見た目の劣化は、人間の目ではほとんど区別できません。しかし、Lighthouseのスコアと、Vercelの帯域幅コストには劇的な改善が見込まれます。
結論:まずは「止血」しよう
エンジニアとして、私たちはつい「最新のベストプラクティス(今回ならAstro Assets)」に固執しがちです。 しかし、その移行コストが障壁となって改善が先送りになるなら、それは本末転倒です。
まずは現在の public フォルダを最適化して「止血」する。
完全な移行は、そのあとでゆっくり考えればいいのです。
もしあなたのプロジェクトにも肥大化した public フォルダがあるなら、ぜひこのスクリプトを試してみてください。5秒後には、世界が変わっているはずです。






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