CDNエッジリサイズの落とし穴 2025 — アップスケール/キャッシュ/画質の三角形

公開: 2025年9月23日 · 読了目安: 5 · 著者: Unified Image Tools 編集部

はじめに

エッジでの画像リサイズフォーマット変換は強力ですが、設計を誤ると「無限のバリアント」「キャッシュ断片化」「品質劣化」の三重苦に陥ります。本稿は、現場で頻出する落とし穴を体系化し、安全に運用するためのガードレールを提示します。

TL;DR

  • アップスケール抑制と原寸以上の禁止をデフォルトに
  • DPR/フォーマットの自動分岐はキャッシュキー設計とセットで
  • 品質監視は差分/指標/目視の3点チェック

内部リンク: エッジ時代の画像配信最適化 CDN 設計 2025, 2025年のリサイズ設計 — レイアウトから逆算して 30–70% の無駄を削る

なぜ落とし穴が生まれるのか

  • クエリ式 API(w, h, q, fmt, bg, fit...)が柔軟すぎて、バリアントが指数的に増殖
  • Accept/DPR/言語/地域などのネゴシエーションでキャッシュキーが爆発
  • 元画像の解像度や色空間が一定でないため、変換時に画質/色の破綻が混入

回避には「入力(元画像)と出力(バリアント)を標準化し、作れるサイズ/フォーマットを限定」する思想が必要です。

典型アンチパターンと対策

  • クエリを自由入力にして無制限の w/h/q/fmt を許可 → 許可リスト方式に変更(例: WIDTHS=[320,480,720,960,1280,1536])
  • DPR=3 端末に合わせて幅×3 を無制限出力 → 実効表示幅×DPR を上限化(かつ原寸以上禁止)
  • Vary: * 的な拡張でキャッシュが断片化 → Vary: Accept のみに絞り、DPR はクエリ/パスで管理
  • 自動フォーマット(AVIF/WebP/JP2)を優先しすぎ → 画質の下限(q/psy/シャープ)をガード、静止画/線画は別プリセット
  • メタデータ維持の方針なし → 著作権/色空間/ICC プロファイル喪失。EXIF/ICC の保持/除去をスイッチで制御

キャッシュキー設計(安全な分割)

キーの構成例:

<origin-path>?w=<width>&fmt=<format>&dpr=<dpr>
  • 必須: w は許可リストに丸め、dpr{1,2,3} のいずれか
  • fmtavif/webp/jpeg/png 等の限定セット
  • q はサーバ側プリセット名(soft, photo, line, ui)で受け取り、数値は受けない

HTTP ヘッダー例:

Cache-Control: public, max-age=31536000, immutable
Vary: Accept
Content-Type: image/avif

Accept による分岐は同じ URL に複数フォーマットをぶら下げられる利点がある一方、curl 等のデバッグで分かりにくくなります。可観測性のためにレスポンスヘッダーへ決定情報(X-Format, X-Width, X-DPR)を出すと良いです。

エッジ実装(擬似コード)

Cloudflare Workers 風の疑似実装:

const WIDTHS = [320, 480, 720, 960, 1280, 1536];

function clampWidth(w) {
  const n = Math.max(...WIDTHS.filter((x) => x <= w));
  return n ?? WIDTHS[0];
}

export default {
  async fetch(req) {
    const url = new URL(req.url);
    const accept = req.headers.get('Accept') || '';
    const dpr = Math.min(3, Math.max(1, Number(url.searchParams.get('dpr') || 1)));
    const desired = Number(url.searchParams.get('w') || 0);
    const width = clampWidth(desired);

    // 原寸以上禁止(例: originWidth はメタから取得)
    const originWidth = await getOriginWidth(url.pathname);
    const target = Math.min(width * dpr, originWidth);

    const fmt = accept.includes('image/avif') ? 'avif'
              : accept.includes('image/webp') ? 'webp'
              : 'jpeg';

    const res = await transformAtEdge({
      path: url.pathname,
      width: target,
      format: fmt,
      preset: choosePreset(url),
      noUpscale: true,
    });

    return new Response(res.body, {
      headers: {
        'Content-Type': `image/${fmt}`,
        'Cache-Control': 'public, max-age=31536000, immutable',
        Vary: 'Accept',
        'X-Width': String(target),
        'X-Format': fmt,
        'X-DPR': String(dpr),
      },
    });
  },
};

ガードレール(運用ポリシー)

  • 原寸超え禁止(withoutEnlargement)を常時オン。リクエストは最も近い許可幅に丸める
  • 代表幅は 4〜6 段に限定、sizes 設計と揃える(過剰ディメンションを作らない)
  • フォーマットは avif→webp→jpeg の順でフォールバック、一貫した色空間(sRGB)へ変換
  • q の直指定は禁止。プリセット(photo/line/ui)で画質・シャープを包括設定
  • 変換失敗/タイムアウト時は原画像を返し、監視に上げる(静的フェイルオープン)

品質監視(差分 + 指標 + 目視)

  • 差分: 代表 30 枚のゴールデンセットでビットマップ比較(SSIM/LPIPS)
  • 指標: 転送量/デコード時間/LCP の p75 を定点観測(RUM)
  • 目視: 線画/文字/グラデ/肌の 4 カテゴリで QC(qsharpness を調整)

サンプル(Node, sharp):

import sharp from 'sharp';

async function ssimLike(a, b) {
  const [A, B] = await Promise.all([
    sharp(a).resize(800).raw().toBuffer(),
    sharp(b).resize(800).raw().toBuffer(),
  ]);
  // ここに SSIM 近似ロジックを実装(省略)。閾値を 0.95 などに設定
}

ケーススタディ(短編)

事例1: 無制限クエリでキャッシュが壊滅

  • 症状: ほぼ毎回ミス(MISS)。オリジン負荷が跳ね上がる
  • 対策: 許可リストへ丸め、DPR は {1,2} のみ許容、q はプリセット化
  • 結果: ヒット率 20%→75%、LCP も 6% 改善

事例2: 高精細端末でのアップスケール劣化

  • 症状: DPR=3 が原寸を超えてリクエスト、文字がにじむ
  • 対策: 原寸超え禁止 + 表示幅×DPR で上限化
  • 結果: 文字にじみ解消、INP のロングタスクも減少

FAQ

Q. 自動フォーマットは常に有効?

A. ほぼ有効ですが、ポスター化が目立つ線画/文字には JPEG(高 q + シャープ)を使うなどプリセットで分岐を。

Q. Vary: DPR は付けるべき?

A. 推奨しません。URL(w, dpr)で明示し、Vary: Accept に限定する方がキャッシュ予測性が上がります。

Q. 代表幅は何段が良い?

A. 多すぎると断片化します。4〜6 段が現実解。sizes と揃えて全体最適化を。

チェックリスト(配信用)

  • [ ] w は許可リストに丸め、原寸超え禁止
  • [ ] dpr{1,2,(3)} まで、表示幅×DPR の上限を実装
  • [ ] fmt は限定セット、Vary: Accept のみ
  • [ ] q は数値で受けずプリセット名にマップ
  • [ ] sRGB 変換/ICC 処理の一貫性を担保
  • [ ] ゴールデンセットで差分/目視の QC を定常運用

関連ツール

関連記事

基礎

画像最適化の基本 2025 — 勘に頼らない土台づくり

どのサイトにも効く、速くて美しい配信のための最新ベーシック。リサイズ→圧縮→レスポンシブ→キャッシュの順で安定運用に。

Web

エッジ時代の画像配信最適化 CDN 設計 2025

エッジ/CDNでの画像配信を高速・安定・省トラフィックにする設計ガイド。キャッシュキー、Vary、Acceptネゴシエーション、Priority Hints、Early Hints、プリコネクトまで総合解説します。

基礎

画像のA/Bテスト設計 2025 — 画質・速度・CTRを同時に最適化

フォーマット/画質/サイズ/プレースホルダーの組み合わせを、LCP/INPとCTRで評価し、実運用に落とすテスト設計。

Web

画像配信のCache-ControlとCDN無効化 2025 — 早く・壊さず・確実に刷新

immutable/short-max-age/stale-while-revalidate/バージョニング/ETag運用で、キャッシュ効率と確実な更新を両立する実装ガイド。

比較

画像の画質評価指標 SSIM/PSNR/Butteraugli 実践ガイド 2025

機械的な数値指標をうまく活用して、圧縮やリサイズ後の画質を客観的に比較・検証するための実践手順をまとめます。SSIM/PSNR/Butteraugli の使い分けと注意点、ワークフローへの組み込み例まで。

リサイズ

2025年のリサイズ設計 — レイアウトから逆算して 30–70% の無駄を削る

レイアウトに基づく目標幅の導出、複数サイズの生成、srcset/sizes の実装まで。最も効く削減手法を体系化。