image-optimization-cdn について
EC 開発者向けの画像最適化ガイドです。Shopify、WooCommerce、BigCommerce、自社構築ストアでのサイズ調整、最新形式、CDN 配信、遅延読み込みを扱います。
主な機能
- 01プラットフォーム別の最適化方式
- 02画像サイズと形式の変換
- 03CDN による画像配信
- 04レスポンシブ画像の設定
- 05遅延読み込みと LCP 対策
活用シーン
- 01商品ページの画像表示を改善
- 02ヘッドレスストアの画像パイプライン構築
- 03JPEG/PNG のストアへの WebP 対応追加
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ finsilabs/ awesome-ecommerce-skills --skill image-optimization-cdnインストール後、Claude Code で/image-optimization-cdnを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
tile.jsonevals/cdn-cache-strategy-and-image-upload-pipe/criteria.jsontask.mdevals/responsive-image-loading-and-lcp-optimiz/criteria.jsontask.mdevals/sharp-pipeline-configuration/criteria.jsontask.md詳細
画像最適化 CDN
概要
商品画像は通常、EC ページで最も容量の大きいアセットであり、Largest Contentful Paint(LCP)のスコア低下の最大の原因です。画像最適化パイプラインでは、画像を必要な寸法にリサイズし、最新の形式(WebP、AVIF)に変換してファイルサイズを30~50%削減し、ユーザーに近い CDN から配信し、ファーストビューより下にある画像には遅延読み込みを適用します。
このスキルを使用するタイミング
- 大きすぎる画像や最適化されていない画像が原因で、商品ページの Core Web Vitals が基準を満たさない場合(LCP > 2.5s)
- 新しいヘッドレスストアフロント向けに画像パイプラインを設定する場合
- ベンダーから提供された元の商品画像が数 MB に及び、自動処理が必要な場合
- JPEG/PNG のみを配信する既存のストアフロントに WebP/AVIF のサポートを追加する場合
- Core Web Vitals を測定した結果、画像が主なボトルネックになっている場合
基本的な手順
ステップ 1:プラットフォームと利用可能な画像最適化機能を確認する
| プラットフォーム | 組み込みの画像 CDN | 必要な作業 |
|---|---|---|
| Shopify | Shopify CDN(Fastly)— WebP 変換を自動処理し、URL パラメーターでレスポンシブなサイズ変更に対応 | Liquid の img_url フィルターでサイズパラメーターを使用し、すべての <img> タグに width と height 属性があることを確認し、ファーストビューより下の画像に loading="lazy" を追加します |
| WooCommerce | デフォルトではなし | WebP 変換を自動処理する Imagify または ShortPixel プラグインをインストールし、グローバル配信のために CDN(無料プラン)として Cloudflare を追加します |
| BigCommerce | BigCommerce CDN — WebP 変換を自動処理し、レスポンシブなサイズ変更に対応 | BigCommerce は画像を CDN 経由で自動配信します。URL で画像の寸法を指定し、テンプレートで適切な <img> 属性を設定して最適化します |
| カスタム/ヘッドレス | なし — 自分で構築します | CDN とともに Cloudinary(マネージド)または Sharp(セルフホスト可能)を使用します。実装方法は以下をご覧ください |
ステップ 2:プラットフォーム固有の画像最適化
Shopify
Shopify CDN は WebP への変換とリサイズを自動処理します。 Liquid テンプレートで正しく利用してください。
- 必ず
image_urlフィルターと明示的な寸法を使用します。
<img
src="{{ product.featured_image | image_url: width: 800 }}"
srcset="{{ product.featured_image | image_url: width: 400 }} 400w,
{{ product.featured_image | image_url: width: 800 }} 800w,
{{ product.featured_image | image_url: width: 1200 }} 1200w"
sizes="(max-width: 640px) 100vw, 50vw"
width="800" height="800"
loading="lazy"
alt="{{ product.featured_image.alt | escape }}"
/>
loading="eager"を使用するのは、ヒーロー(LCP)画像だけにします。
{% if forloop.first %}
{%- assign loading = 'eager' -%}
{%- assign fetchpriority = 'high' -%}
{% else %}
{%- assign loading = 'lazy' -%}
{%- assign fetchpriority = 'auto' -%}
{% endif %}
<img loading="{{ loading }}" fetchpriority="{{ fetchpriority }}" ... />
- LCP スコアを確認します。
- Shopify 管理画面で オンラインストア → テーマ に移動し、レポートを表示 をクリックします。
- または、Google PageSpeed Insights(pagespeed.web.dev)で商品ページとコレクションページを測定します。
- モバイルでスコアが75未満の場合、ヒーロー画像に
fetchpriority="high"が必要か、画像が大きすぎる可能性があります。
WooCommerce
WooCommerce は、デフォルトでは CDN や WebP 変換を使わず、サーバーから画像を配信します。次の手順が必要です。
ステップ 1:WebP 変換を追加する(いずれかを選択):
- Imagify(無料プラン:月 25 MB):WordPress.org からインストールし、メディア → Imagify に移動して一括最適化ウィザードを実行します。既存の画像がすべて WebP に変換され、新しいアップロード画像も自動で変換されるようになります
- ShortPixel(無料プラン:月 100 クレジット):同様の手順です。プラグインとしてインストールし、アップロード時に画像を変換します
ステップ 2:CDN 配信を追加する:
- Cloudflare(無料):Cloudflare にドメインを追加すると、すべての画像(およびその他のアセット)が Cloudflare の世界 300 か所以上のエッジロケーションから自動的に配信されます
- BunnyCDN(1 TB あたり 1 ドル):トラフィックの多いストアでは、無料の Cloudflare よりも細かく制御でき、パフォーマンスも向上します。BunnyCDN WordPress プラグインで設定します
ステップ 3:遅延読み込みを設定する(WordPress 5.5 以降では自動で処理されます):
WordPress 5.5 以降では、すべての <img> タグに loading="lazy" が自動的に追加されます。次の手順で動作を確認します。
- 商品ページのソースを表示します
- ヒーロー画像以外の画像に
loading="lazy"属性があることを確認します - ヒーロー画像または最初の商品画像には
loading="eager"を指定する必要があります。テーマのテンプレートで設定します
ステップ 4:WooCommerce で画像の寸法を正しく設定する:
- WooCommerce → 設定 → 商品 → 表示 に移動します
- テーマで実際に表示される寸法に合わせて画像サイズを設定します。サイズの大きすぎる画像は帯域幅を無駄に消費します
- サイズを変更した後、WP CLI または Regenerate Thumbnails プラグインを使って既存の画像をリサイズします:
wp media regenerate --only-missing
BigCommerce
BigCommerce は、対応ブラウザー向けに WebP へ自動変換し、すべての画像を CDN 経由で配信します。テーマのテンプレートを最適化します。
- Storefront → My Themes → Edit Theme Files で、商品ページのテンプレートを開きます
getImageSrcsetHandlebars ヘルパーを使って、レスポンシブなsrcset属性を生成します:
<img
src="{{getImage image 'product_size'}}"
srcset="{{getImageSrcset image 1x='product_size' 2x='zoom_size'}}"
loading="lazy"
width="800" height="800"
alt="{{image.alt}}"
/>
- Storefront → My Themes → Customize で、デザイン上の表示寸法に合うように画像サイズを設定します
- ストアに対して Google PageSpeed Insights を実行し、画像に関する推奨事項があれば対応します
カスタム/ヘッドレス
オプション A:Cloudinary(マネージド型 — ほとんどのストアに推奨)
- cloudinary.com で登録します(無料プラン:ストレージ 25 GB、月間帯域幅 25 GB)
- 商品画像を Cloudinary にアップロードするよう、ストアを設定します:
// lib/cloudinary.js
import { v2 as cloudinary } from 'cloudinary';
cloudinary.config({ cloud_name: process.env.CLOUDINARY_CLOUD_NAME, api_key: ..., api_secret: ... });
export function getProductImageUrl(publicId, { width, height }) {
return cloudinary.url(publicId, {
transformation: [{
width, height, crop: 'fill', gravity: 'auto',
quality: 'auto:good',
fetch_format: 'auto', // Auto-serves WebP/AVIF based on Accept header
}],
secure: true,
});
}
- 商品画像コンポーネントでレスポンシブな
srcsetを生成します:
export function ProductImage({ publicId, alt, priority = false }) {
const widths = [240, 400, 600, 800, 1200];
const srcSet = widths.map(w => `${getProductImageUrl(publicId, { width: w, height: w })} ${w}w`).join(', ');
return (
<img
src={getProductImageUrl(publicId, { width: 400, height: 400 })}
srcSet={srcSet}
sizes="(max-width: 640px) 100vw, (max-width: 1024px) 50vw, 25vw"
alt={alt}
width={400} height={400}
loading={priority ? 'eager' : 'lazy'}
fetchPriority={priority ? 'high' : 'auto'}
/>
);
}
オプション B:Sharp(セルフホスト型の画像処理)
セルフホスト型のパイプラインには Sharp を使用します。アップロード時に画像を処理し、オブジェクトストレージ(S3、R2、Backblaze)にバリエーションを保存します。
// lib/image-processor.js
import sharp from 'sharp';
const SIZES = {
thumbnail: { width: 240, height: 240 },
card: { width: 400, height: 400 },
detail: { width: 800, height: 800 },
zoom: { width: 1600, height: 1600 },
};
export async function processAndStoreProductImage(inputBuffer, productId) {
const urls = {};
for (const [sizeName, dims] of Object.entries(SIZES)) {
const webpBuffer = await sharp(inputBuffer)
.rotate() // auto-rotate from EXIF
.resize({ ...dims, fit: 'cover', withoutEnlargement: true })
.webp({ quality: 80 })
.toBuffer();
const key = `products/${productId}/${sizeName}.webp`;
await uploadToStorage(key, webpBuffer, 'image/webp');
urls[sizeName] = `https://images.yourstore.com/${key}`;
}
return urls;
}
不変の CDN ヘッダーを付けて配信します(キャッシュを更新できるよう、ファイル名にコンテンツハッシュを含めます)。
// Image URL response headers
res.setHeader('Cache-Control', 'public, max-age=31536000, immutable');
ベストプラクティス
- すべての
<img>タグに明示的なwidthとheightを設定する — 画像の読み込み前にスペースを確保し、累積レイアウトシフト(CLS)を防ぎます loading="eager"とfetchpriority="high"は LCP 画像のみに使用する — すべての画像にeagerを適用すると遅延読み込みの効果がなくなり、初期ページのデータ量が増加します- 画像を拡大しない — 幅 400px のコンテナーに 2000px の画像を配信すると帯域幅を無駄に消費します。Sharp では
withoutEnlargement: trueを使用するか、Cloudinary の幅・高さパラメーターを指定します - URL バージョニングで CDN キャッシュを無効化する — 画像のファイル名にコンテンツハッシュまたはバージョン番号を含めます。URL の変更は、CDN キャッシュを手動で消去するより確実です
- リアルユーザーモニタリング(RUM)を使って本番環境の LCP を監視する — ラボテスト(Lighthouse)はキャッシュが空の状態で LCP を測定しますが、RUM では実際のユーザー体験を把握できます
よくある落とし穴
| 問題 | 解決策 |
|---|---|
| LCP 画像が想定していたものではない | Chrome DevTools の「Performance」タブを使って、実際の LCP 要素を特定してください。商品画像ではなく、背景画像やヒーローバナーの場合があります |
| オンデマンド変換で AVIF のエンコードが遅すぎる | アップロード時に AVIF を事前生成し、リアルタイム変換には WebP を使用してください(エンコード速度は AVIF の 50 倍です) |
| 本番環境に Sharp のネイティブバイナリがない | sharp を devDependencies ではなく dependencies に追加してください。Docker ビルドでは、ターゲットのアーキテクチャに合わせてください:npm install --platform=linux --arch=x64 sharp |
| CDN からの画像が初回リクエスト時に読み込まれない | アップロード後に CDN の URL を取得して、主要な商品画像のキャッシュを事前にウォームアップしてください。最初の訪問者によるキャッシュのウォームアップに頼らないでください |
| WooCommerce の画像が WebP に変換されない | ホスティング環境が GD または Imagick の PHP 拡張機能に対応していることを確認してください(どちらも Imagify/ShortPixel に必要です)。利用できない場合は、ホストに問い合わせてください |
関連スキル
- @ecommerce-caching
- @edge-commerce
- @monitoring-alerting-commerce
- @responsive-storefront