画像最適化パイプライン自動化:CI/CD + ワーカー + キャッシュ設計 実務パターン
画像は“増える前提”で仕組み化しないと管理負債になります。手動ドラッグ圧縮から抜け出す第一歩は 差分判定 + 自動再生成
。ここでは最小パターンを提示し、段階的な高度化ポイントを示します。
当サイトの一部ツール(/batch-optimize など)と同じ WASM / ブラウザ最適化ロジックをワーカーへ移植する構成も可能です。
TL;DR
- 差分のみ再エンコード
- content-hash + immutable 配信
- 遅い生成 (AVIF) は非同期・WebP 先行
1. 最小アーキテクチャ
- 開発者が PR に
raw/
へ元画像追加(命名規約 enforced) - CI がフォーマット変換 (AVIF/WebP/PNG) + メタ最適化(EXIF除去)
- 出力を
dist/images/
へ配置しサイズ差分コメント - main マージで CDN へ incremental upload(content-hash)
- 利用側はパス規約 +
<picture>
テンプレで自動解決
2. 差分判定スクリプトひな型
// pseudo build script import fs from 'node:fs/promises'; import { optimize } from './lib/opt'; import ArticleNextPrev from '@/components/ArticleNextPrev'; const SRC = 'raw'; const OUT = 'dist/images'; async function run(){ const files = (await fs.readdir(SRC)).filter(f=>/.(png|jpe?g)$/i.test(f)); await fs.mkdir(OUT,{recursive:true}); for (const f of files){ const src = SRC + '/' + f; const base = f.replace(/.(png|jpe?g)$/i,''); const buf = await fs.readFile(src); const variants = await optimize(buf,{ avif:true, webp:true, png:true }); for (const v of variants){ const out = OUT + '/' + base + '.' + v.ext; const prev = await fs.stat(out).catch(()=>null); if (!prev || prev.size !== v.data.length){ await fs.writeFile(out,v.data); console.log('updated', out, (v.data.length/1024).toFixed(1)+'KB'); } } } } run();
3. キャッシュ & 配信
生成成果物は content-hash
含みパス(例: logo.abcd1234.avif
)で immutable
配信。既存 URL 互換を守る必要がある場合はマニフェストJSONを生成しアプリ起動時にマップをロード。
4. 高度化ロードマップ
- 画像差分(SSIM)で“視覚無変化”判定し再生成省略
- CDN エッジワーカー内で on-demand 最適化(ホットパスのみ永続化)
- AI 背景除去 / パレット抽出を事前生成し検索性向上
5. CI ワークフロー構成例
name: images
on: [pull_request]
jobs:
optimize:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with: { node-version: 20 }
- run: npm ci --no-audit --no-fund
- name: Build variants
run: node scripts/build-images.js --changed > sizes.json
- name: Comment diff
if: always()
run: node scripts/comment-size-diff.js sizes.json
deploy:
needs: optimize
if: github.event.pull_request.merged == true
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npm ci
- run: node scripts/upload-changed.js --dir dist/images --bucket CDN_BUCKET
PR 上で差分見える化 → main マージ時のみアップロード。
6. メトリクス & 閾値
- bytes削減率: 目標中央値 ≥ 35% (旧JPEG基準)。
- SSIM: 基本 0.985 以上。UI/テキスト画像は 0.992 以上。
- ΔE2000 (基準JPEG): 平均 < 2.0 / 最大 < 4.0。
- 処理時間/枚: P95 1.2s (AVIF effort4) を超えたらワーカー並列見直し。
- 失敗率: エンコード失敗 < 0.5%。連続失敗はフォーマット除外。
// metrics-log.ts (抜粋)
export interface MetricRow { name:string; bytes:number; bytesOrig:number; ssim:number; deltaE:number; ms:number; }
// 集計: 中央値/最大/失敗率 など計算し CI で閾値超過なら失敗。
7. スケール & コスト試算
1日追加 2,000 枚 / 平均 1.2MB → AVIF/WebP/PNG 生成後 平均 420KB 配信想定。AVIF effort を 4 で固定し CPU 秒を抑制、遅延影響を避ける大物 (≥4K) はキュー投入し WebP を先行。ストレージは original + hashed variants
で 3.2x 膨らむ想定 → 90 日アクセスなしをライフサイクルで低冗長ストレージへ移行。
8. トラブルシュート
- 生成遅延: AVIF 数を制限し priority queue / WebP fallback 即時。
- キャッシュ汚染: content-hash 漏れ → 旧 URL 上書き。manifest 再生成で差分確認。
- 品質劣化報告: SSIM は閾値内 → ΔE 高 → 彩度強い画像で AVIF 設定再調整。
- サイズ伸び: PNG 最適化ツール (oxipng/wasm) バージョン戻しで回帰切り分け。
9. 次の拡張
- 画像ごと
quality hint
付与 (低頻度/高露出/高ノイズ) - オンデマンド変換ログをヒートマップ化しバッチ候補抽出
- Perceptual diff (SSIM + LPIPS) 混合指標で過圧縮防止
Version: 2025-09-08 初版 + 詳細節追加。以後は差分判定/視覚評価自動化を追記予定。