実装の全体像
Shopifyの商品画像をCloudinaryから配信するフローは、大きく3つのステップで構成されます。
- 画像の同期: Shopifyの画像をCloudinaryにアップロード
- URL変換: Shopify画像URLをCloudinary URLに変換
- フロントエンド表示: Next.jsで最適化された画像を表示
商品作成/更新
画像URLを抽出
変換・保存
画像リクエスト
変換済み画像を返却
最適化された画像を表示
ステップ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同期に移行するのもおすすめです。