Shopify → Cloudinary配信フローの構築

画像の同期からNext.jsでの表示まで、実装の全体像を解説

CloudinaryShopify画像同期Next.js実装
読了時間: 13分

実装の全体像

Shopifyの商品画像をCloudinaryから配信するフローは、大きく3つのステップで構成されます。

  1. 画像の同期: Shopifyの画像をCloudinaryにアップロード
  2. URL変換: Shopify画像URLをCloudinary URLに変換
  3. フロントエンド表示: Next.jsで最適化された画像を表示
画像の同期フロー
Shopify(商品登録)

商品作成/更新

Webhook
同期処理(Lambda等)

画像URLを抽出

Upload API
Cloudinary(画像保存)

変換・保存

画像配信フロー
お客様のブラウザ

画像リクエスト

CDNアクセス
Cloudinary CDN

変換済み画像を返却

高速配信
高速表示

最適化された画像を表示

ステップ1: 画像の同期

方法A: Webhookによる自動同期(推奨)

Shopifyで商品が登録・更新されたとき、自動的にCloudinaryにアップロードする仕組みです。

Shopify Webhook設定:

  • products/create - 商品作成時
  • products/update - 商品更新時

同期処理の流れ:

1. Shopifyで商品登録
2. Webhook発火 → あなたのエンドポイントにPOST
3. 商品データから画像URLを抽出
4. Cloudinary Upload APIで画像をアップロード
5. Cloudinary URLをどこかに保存(後述)

サンプルコード(概念):

// Webhook受信エンドポイント
async function handleProductWebhook(shopifyProduct: ShopifyProduct) {
  for (const image of shopifyProduct.images) {
    // Shopify画像URLからCloudinaryにアップロード
    const result = await cloudinary.uploader.upload(image.src, {
      folder: `shopify/${shopifyProduct.id}`,
      public_id: image.id.toString(),
      overwrite: true,
    });

    // Cloudinary URLを保存(メタフィールドやDBなど)
    await saveCloudinaryUrl(shopifyProduct.id, image.id, result.secure_url);
  }
}

方法B: フェッチ型(オンデマンド)

事前に同期せず、リクエスト時にCloudinaryがShopifyから画像を取得する方法です。

Cloudinary Fetch URL形式:

https://res.cloudinary.com/your-cloud/image/fetch/w_400,f_auto,q_auto/https://cdn.shopify.com/...

/fetch/ の後にShopify画像のURLを指定すると、Cloudinaryがその画像を取得・変換・キャッシュしてくれます。

メリット:

  • 事前同期が不要
  • 実装がシンプル

デメリット:

  • 初回リクエストが遅い(Shopifyからの取得が入る)
  • Shopify画像URLが変わると再取得が必要

ステップ2: URL変換ロジック

フロントエンドで使うために、Shopify画像URLをCloudinary URLに変換するユーティリティを作ります。

アップロード型の場合

// Shopify商品IDと画像IDからCloudinary URLを生成
function getCloudinaryUrl(
  productId: string,
  imageId: string,
  options: {
    width?: number;
    height?: number;
    format?: 'auto' | 'webp' | 'avif';
    quality?: 'auto' | number;
  } = {}
): string {
  const { width, height, format = 'auto', quality = 'auto' } = options;

  const transforms: string[] = [];
  if (width) transforms.push(`w_${width}`);
  if (height) transforms.push(`h_${height}`);
  transforms.push(`f_${format}`);
  transforms.push(`q_${quality}`);

  const transformStr = transforms.join(',');

  return `https://res.cloudinary.com/${CLOUD_NAME}/image/upload/${transformStr}/shopify/${productId}/${imageId}`;
}

// 使用例
const imageUrl = getCloudinaryUrl('12345', '67890', {
  width: 400,
  format: 'auto',
  quality: 'auto',
});
// → https://res.cloudinary.com/your-cloud/image/upload/w_400,f_auto,q_auto/shopify/12345/67890

フェッチ型の場合

function getCloudinaryFetchUrl(
  shopifyImageUrl: string,
  options: {
    width?: number;
    format?: 'auto' | 'webp';
    quality?: 'auto' | number;
  } = {}
): string {
  const { width, format = 'auto', quality = 'auto' } = options;

  const transforms: string[] = [];
  if (width) transforms.push(`w_${width}`);
  transforms.push(`f_${format}`);
  transforms.push(`q_${quality}`);

  const transformStr = transforms.join(',');

  return `https://res.cloudinary.com/${CLOUD_NAME}/image/fetch/${transformStr}/${encodeURIComponent(shopifyImageUrl)}`;
}

ステップ3: Next.jsでの表示

カスタムImageコンポーネント

Next.jsのImageコンポーネントと組み合わせて、Cloudinary URLを使うカスタムコンポーネントを作ります。

// components/CloudinaryImage.tsx
import Image from 'next/image';

interface CloudinaryImageProps {
  productId: string;
  imageId: string;
  alt: string;
  width: number;
  height: number;
  priority?: boolean;
}

// Cloudinary用のローダー
const cloudinaryLoader = ({ src, width, quality }: {
  src: string;
  width: number;
  quality?: number;
}) => {
  const q = quality || 'auto';
  // srcにはproductId/imageIdの形式が入る想定
  return `https://res.cloudinary.com/${CLOUD_NAME}/image/upload/w_${width},f_auto,q_${q}/${src}`;
};

export function CloudinaryImage({
  productId,
  imageId,
  alt,
  width,
  height,
  priority = false,
}: CloudinaryImageProps) {
  return (
    <Image
      loader={cloudinaryLoader}
      src={`shopify/${productId}/${imageId}`}
      alt={alt}
      width={width}
      height={height}
      priority={priority}
    />
  );
}

next.config.jsの設定

Cloudinaryのドメインを許可します。

// next.config.js
module.exports = {
  images: {
    domains: ['res.cloudinary.com'],
    // または loader を使う場合
    loader: 'custom',
    loaderFile: './lib/cloudinary-loader.ts',
  },
};

構成パターンの比較

Webhook同期
初回速度◎ 速い
実装の手間△ 多い
運用の手間○ 少ない
おすすめ度★★★
フェッチ型
初回速度△ 遅い
実装の手間◎ 少ない
運用の手間◎ ほぼなし
おすすめ度★★
ハイブリッド
初回速度○ 中間
実装の手間△ 多い
運用の手間△ 中間
おすすめ度★★

おすすめ: Webhook同期

本格的なECサイトでは、Webhook同期がおすすめです。

  • 初回アクセスでも高速
  • 画像がCloudinaryに確実に存在する
  • Shopify側の画像URLが変わっても影響なし

手軽に始めるなら: フェッチ型

まず試してみたい、小規模サイトで十分、という場合はフェッチ型が手軽です。

実装時のTips

1. プレースホルダー画像を活用

Cloudinaryは低画質プレースホルダー(LQIP)を簡単に生成できます。

w_20,f_auto,q_auto,e_blur:500

これで幅20pxのぼかし画像を取得し、本画像の読み込み中に表示できます。

2. レスポンシブ対応

srcsetを使って複数サイズを指定します。

const sizes = [320, 640, 960, 1280];
const srcSet = sizes
  .map(size => `${getCloudinaryUrl(productId, imageId, { width: size })} ${size}w`)
  .join(', ');

3. エラーハンドリング

Cloudinary側でエラーが発生した場合のフォールバックを用意します。

<Image
  src={cloudinaryUrl}
  onError={(e) => {
    // Shopifyの元画像にフォールバック
    e.currentTarget.src = shopifyOriginalUrl;
  }}
  alt={alt}
/>

まとめ

Cloudinaryを使った画像配信フローを構築することで:

  • サーバー負荷: ゼロに削減
  • コスト: 予測可能で最適化可能
  • 表示速度: グローバルCDNで高速化
  • 変換機能: 高度な画像処理を活用可能

ヘッドレスECの画像配信問題に悩んでいる場合、Cloudinaryは有力な選択肢です。まずはフェッチ型で試してみて、効果を実感してからWebhook同期に移行するのもおすすめです。

関連記事