Skip to main content

時の絵 バリエーション事前生成管理システム

Next.jsヘッドレス版・バリエーション管理詳細設計
1作品あたり最大23バリエーションの事前生成・管理システム

システム概要

基本設計思想

  • 事前生成方式: 全バリエーション画像を予め生成・保存
  • 瞬時切替UX: リアルタイム合成なし、即座のバリアント表示
  • Shopify標準機能活用: バリアント・在庫・価格管理を標準機能で実現
  • 効率的運用: バッチ処理による計画的リソース使用

データ分離戦略: Shopify vs R2(重要)

基本方針

  • Shopify: 商品表示用Web最適化画像(800x600px, 72dpi, WebP/JPEG)
  • R2: 印刷用高解像度データ(300dpi以上, TIFF/PSD形式)
  • 工場システム: R2から直接印刷用データを取得

R2ストレージ構造

/print_data/{artwork_id}/              # 印刷用高品質データ
├── original_hires.tiff # 原画(300dpi以上)
├── premium_frame/
│ ├── natural_white_print.tiff # 印刷用(300dpi, CMYK)
│ └── ... (全16種印刷用)
├── wooden_frame/
│ └── natural_print.tiff, brown_print.tiff, black_print.tiff
├── paper_stand/
│ └── navy_print.tiff, black_print.tiff, white_print.tiff
└── postcard/
├── front_print.tiff # 表面印刷用
└── back_print.tiff # 裏面印刷用(作家名入り)

/display_data/{artwork_id}/ # Web表示用最適化データ
├── web_optimized/
│ ├── natural_white_web.jpg # Shopify登録用(Web最適化)
│ └── ... (全16種 Web用)

生成フロー

  1. 高解像度原画 → R2にアップロード
  2. バリエーション生成: 同時に2種類作成
    • 印刷用高解像度版 → R2 /print_data/
    • Web表示用最適化版 → R2 /display_data/ → Shopify登録
  3. 品質保証: 両バージョンの整合性チェック

商品タイプとバリエーション設計

1. プレミアムフレーム(デフォルト選択)

interface PremiumFrameVariants {
frame_colors: ['natural', 'silver', 'brown', 'black']; // 4色
mat_colors: ['white', 'natural', 'indigo', 'black']; // 4色
total_combinations: 16; // 4 × 4

// 生成画像例
variants: [
'natural_white.jpg', 'natural_natural.jpg', 'natural_indigo.jpg', 'natural_black.jpg',
'silver_white.jpg', 'silver_natural.jpg', 'silver_indigo.jpg', 'silver_black.jpg',
'brown_white.jpg', 'brown_natural.jpg', 'brown_indigo.jpg', 'brown_black.jpg',
'black_white.jpg', 'black_natural.jpg', 'black_indigo.jpg', 'black_black.jpg'
];
}

2. 木製フレーム

interface WoodenFrameVariants {
frame_colors: ['natural', 'brown', 'black']; // 3色(マットなし)
total_combinations: 3;

variants: [
'natural.jpg', 'brown.jpg', 'black.jpg'
];
}

3. ペーパースタンド

interface PaperStandVariants {
colors: ['navy', 'black', 'white']; // 3色
total_combinations: 3;

variants: [
'navy.jpg', 'black.jpg', 'white.jpg'
];
}

4. ポストカード

interface PostcardVariants {
sides: ['front', 'back']; // 表面・裏面
total_combinations: 1; // 1商品(2画像セット)

variants: [
'front.jpg', // 表面(作品画像)
'back.jpg' // 裏面(作家名・作品名入り)
];
}

データベース設計(Cloudflare D1)

作品マスタ(非PII)

CREATE TABLE tokinoe_artworks (
id TEXT PRIMARY KEY,
brand TEXT NOT NULL DEFAULT 'tokinoe',
title TEXT NOT NULL,
artist TEXT,
period TEXT,
technique TEXT,
description TEXT,

-- R2オリジナル画像キー
original_image_key TEXT NOT NULL,

-- Shopify商品ID(商品タイプ別)
shopify_premium_product_id TEXT,
shopify_wooden_product_id TEXT,
shopify_paper_product_id TEXT,
shopify_postcard_product_id TEXT,

-- 生成ステータス
generation_status TEXT DEFAULT 'pending', -- pending/processing/completed/error

-- メタデータ(JSON)
metadata TEXT,

created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
);

バリエーション画像管理(印刷用・Web用分離)

CREATE TABLE tokinoe_variant_images (
id TEXT PRIMARY KEY,
artwork_id TEXT REFERENCES tokinoe_artworks(id),
brand TEXT NOT NULL DEFAULT 'tokinoe',

-- 商品タイプとバリエーション情報
product_type TEXT NOT NULL, -- 'premium_frame', 'wooden_frame', 'paper_stand', 'postcard'
variant_key TEXT NOT NULL, -- 'natural_white', 'brown', 'navy', 'front' など

-- R2画像キー(印刷用・Web用分離管理)
print_image_key TEXT NOT NULL, -- R2印刷用高解像度データパス (/print_data/)
web_image_key TEXT NOT NULL, -- R2 Web表示用データパス (/display_data/)

-- Shopifyバリアント情報(Web用データ連携)
shopify_variant_id TEXT,
shopify_image_id TEXT, -- Shopifyに登録されたWeb用画像のID
shopify_image_url TEXT, -- Shopify CDNのWeb表示用画像URL

-- 生成情報
generation_status TEXT DEFAULT 'pending', -- pending/completed/error
generated_at DATETIME,

created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);

-- インデックス
CREATE INDEX idx_variant_images_artwork_product ON tokinoe_variant_images(artwork_id, product_type);
CREATE INDEX idx_variant_images_status ON tokinoe_variant_images(generation_status);

バリエーション定義マスタ

CREATE TABLE tokinoe_variant_definitions (
id TEXT PRIMARY KEY,
product_type TEXT NOT NULL,

-- バリエーション詳細(JSON)
variant_config TEXT NOT NULL, -- JSON: {frame_colors, mat_colors, etc.}

-- 画像テンプレート情報
template_config TEXT, -- JSON: テンプレート画像のR2キー等

-- 価格情報
base_price INTEGER NOT NULL, -- 基本価格(円)
price_modifiers TEXT, -- JSON: バリエーション別価格調整

active BOOLEAN DEFAULT true,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);

バッチ生成処理システム

1. 管理画面フロー

// 管理画面での一括生成フロー
interface BatchGenerationRequest {
artworks: Array<{
id: string;
original_image_key: string;
product_types: Array<'premium_frame' | 'wooden_frame' | 'paper_stand' | 'postcard'>;
}>;

generation_options: {
force_regenerate: boolean; // 既存画像の強制再生成
product_types_filter?: string[]; // 特定商品タイプのみ生成
};
}

// 処理結果
interface BatchGenerationResult {
total_artworks: number;
total_variants_to_generate: number;

progress: {
completed: number;
failed: number;
pending: number;
};

estimated_completion_time: string;
batch_id: string; // 進捗追跡用
}

2. Cloudflare Workers処理フロー

// Workers Queue処理
interface VariantGenerationTask {
artwork_id: string;
product_type: 'premium_frame' | 'wooden_frame' | 'paper_stand' | 'postcard';
variant_key: string; // 'natural_white', 'brown', 'navy', 'front'

source_image_key: string; // R2の原画像キー
template_config: object; // 額縁・マットのテンプレート情報

output_image_key: string; // 生成先R2キー
batch_id: string;
}

// Queue処理ハンドラー
export default {
async queue(batch: MessageBatch, env: Env): Promise<void> {
for (const message of batch.messages) {
const task: VariantGenerationTask = message.body;

try {
// 1. 原画像をR2から取得
const originalImage = await env.R2_BUCKET.get(task.source_image_key);

// 2. テンプレート画像を取得
const templates = await loadTemplates(task.product_type, task.variant_key);

// 3. 画像合成実行
const compositeImage = await compositeImages(originalImage, templates);

// 4. 生成画像をR2に保存
await env.R2_BUCKET.put(task.output_image_key, compositeImage);

// 5. D1ステータス更新
await updateVariantStatus(env.DB, task.artwork_id, task.variant_key, 'completed');

// 6. 全バリエーション完了時はShopify商品作成
await checkAndCreateShopifyProduct(task);

} catch (error) {
// エラーハンドリング・リトライ・DLQ送信
await handleGenerationError(task, error);
}
}
}
};

3. エラーハンドリング・リトライシステム

interface ErrorHandlingConfig {
retry_policy: {
max_retries: 3;
backoff_strategy: 'exponential'; // 指数バックオフ
initial_delay: 1000; // 初回遅延(ms)
};

dlq_config: {
enabled: true;
max_dlq_retries: 2; // DLQからの最大リトライ
};

error_notifications: {
webhook_url: string; // 運用チームへの通知
slack_channel: string;
};
}

// リトライ・DLQ処理
async function handleGenerationError(task: VariantGenerationTask, error: Error) {
const retryCount = task.retry_count || 0;

if (retryCount < 3) {
// 指数バックオフでリトライ
const delay = Math.pow(2, retryCount) * 1000;
await scheduleRetry(task, delay);
} else {
// DLQに送信
await sendToDLQ(task, error);
// 運用チームに通知
await notifyError(task, error);
}
}

Shopify統合システム

1. 商品・バリアント自動作成

interface ShopifyProductCreation {
// 1作品につき最大4商品(商品タイプ別)
products: Array<{
title: string; // "葛飾北斎「神奈川沖浪裏」- プレミアムフレーム"
product_type: string; // "premium_frame"
vendor: "時の絵 (TOKINOE)";

variants: Array<{
title: string; // "ナチュラル額縁 × ホワイトマット"
price: number;
inventory_quantity: number;
image_id: string; // 事前生成済み画像のShopify画像ID

// メタフィールド
metafields: {
frame_color: string;
mat_color?: string;
variant_key: string;
};
}>;

images: string[]; // R2の事前生成済み画像URL配列
}>;
}

// 自動商品作成処理
async function createShopifyProducts(artwork: Artwork, generatedImages: VariantImage[]) {
const productsByType = groupImagesByProductType(generatedImages);

for (const [productType, images] of Object.entries(productsByType)) {
// 商品作成
const product = await shopifyAdmin.rest.Product.save({
session,
title: `${artwork.artist}${artwork.title}」- ${getProductTypeName(productType)}`,
product_type: productType,
vendor: "時の絵 (TOKINOE)",
images: images.map(img => ({ src: img.cdn_url }))
});

// バリアント作成
for (const image of images) {
const variant = await shopifyAdmin.rest.ProductVariant.save({
session,
product_id: product.id,
title: getVariantTitle(image.variant_key),
price: calculatePrice(productType, image.variant_key),
inventory_quantity: 10, // デフォルト在庫
image_id: findImageId(product.images, image.cdn_url)
});

// メタフィールド設定
await setVariantMetafields(variant.id, image.variant_key);
}
}
}

2. 在庫・価格管理

interface InventoryManagement {
// Shopify標準機能をフル活用
inventory_tracking: {
method: 'shopify'; // Shopify標準在庫管理
auto_reserve: true; // 注文時自動引当
low_stock_alert: 5; // 在庫アラート閾値
};

pricing_strategy: {
base_prices: {
premium_frame: 15000, // プレミアムフレーム基本価格
wooden_frame: 8000, // 木製フレーム基本価格
paper_stand: 3000, // ペーパースタンド基本価格
postcard: 500 // ポストカード基本価格
};

variant_modifiers: {
premium_frame: {
'silver_*': +2000, // シルバー額縁は+2000円
'*_indigo': +500 // 藍色マットは+500円
}
};
};
}

フロントエンド統合(Next.js)

1. 瞬時バリアント切替コンポーネント

// Next.js バリアント選択コンポーネント
interface VariantSelectorProps {
artwork: Artwork;
product_type: 'premium_frame' | 'wooden_frame' | 'paper_stand' | 'postcard';
shopify_product: ShopifyProduct;
}

const VariantSelector: React.FC<VariantSelectorProps> = ({
artwork,
product_type,
shopify_product
}) => {
const [selectedVariant, setSelectedVariant] = useState(shopify_product.variants[0]);

// 事前生成済み画像のマッピング
const variantImages = useMemo(() => {
return shopify_product.variants.reduce((acc, variant) => {
acc[variant.id] = variant.image?.src;
return acc;
}, {} as Record<string, string>);
}, [shopify_product.variants]);

const handleVariantChange = (variantId: string) => {
setSelectedVariant(shopify_product.variants.find(v => v.id === variantId));
// 瞬時画像切替(合成処理なし、事前生成済み画像表示)
};

return (
<div className="variant-selector">
{/* 現在選択中の画像表示 */}
<div className="artwork-preview">
<Image
src={variantImages[selectedVariant.id]}
alt={`${artwork.title} - ${selectedVariant.title}`}
width={800}
height={600}
priority
/>
</div>

{/* バリアント選択UI */}
{product_type === 'premium_frame' && (
<PremiumFrameSelector
variants={shopify_product.variants}
selectedVariant={selectedVariant}
onVariantChange={handleVariantChange}
/>
)}

{/* 価格・在庫表示 */}
<div className="variant-info">
<span className="price">¥{selectedVariant.price}</span>
<span className="stock">在庫: {selectedVariant.inventory_quantity}</span>
</div>

{/* カートに追加 */}
<AddToCartButton variant={selectedVariant} />
</div>
);
};

2. 商品タイプ切替システム

// 商品タイプ切替コンポーネント
const ProductTypeSelector: React.FC<{ artwork: Artwork }> = ({ artwork }) => {
const [selectedType, setSelectedType] = useState<ProductType>('premium_frame');

const productTypes = [
{
key: 'premium_frame',
name: 'プレミアムフレーム',
description: '高品質額縁+マット付き',
default: true
},
{
key: 'wooden_frame',
name: '木製フレーム',
description: 'シンプルな木製額縁'
},
{
key: 'paper_stand',
name: 'ペーパースタンド',
description: '気軽に飾れるスタンド式'
},
{
key: 'postcard',
name: 'ポストカード',
description: '手軽なポストカードサイズ'
}
];

return (
<div className="product-type-selector">
<div className="type-tabs">
{productTypes.map(type => (
<button
key={type.key}
className={`type-tab ${selectedType === type.key ? 'active' : ''}`}
onClick={() => setSelectedType(type.key as ProductType)}
>
<span className="type-name">{type.name}</span>
<span className="type-description">{type.description}</span>
</button>
))}
</div>

{/* 選択中の商品タイプのバリアント選択UI */}
<VariantSelector
artwork={artwork}
product_type={selectedType}
shopify_product={artwork.shopify_products[selectedType]}
/>
</div>
);
};

運用・監視システム

1. 生成進捗監視

interface GenerationMonitoring {
// リアルタイム進捗表示
progress_tracking: {
batch_id: string;
total_tasks: number;
completed: number;
failed: number;
estimated_remaining_time: string;
};

// エラー監視・通知
error_monitoring: {
failed_tasks: FailedTask[];
error_categories: {
'image_processing_error': number;
'r2_upload_error': number;
'shopify_api_error': number;
};

notifications: {
webhook_url: string;
slack_integration: boolean;
};
};

// パフォーマンス監視
performance_metrics: {
average_generation_time_per_variant: number; // ms
queue_processing_rate: number; // variants/minute
error_rate: number; // percentage
};
}

2. 品質管理システム

interface QualityControl {
// 生成画像品質チェック
image_quality_check: {
resolution_validation: boolean; // 最低解像度チェック
file_size_validation: boolean; // ファイルサイズチェック
visual_similarity_score: number; // 原画との類似度スコア
};

// 自動品質改善
quality_improvement: {
auto_retry_on_low_quality: boolean;
quality_threshold: number; // 品質スコア閾値
manual_review_queue: string[]; // 手動レビュー対象
};
}

文書作成日: 2025-08-23
最終更新: 2025-08-23
バージョン: 1.0 - バリエーション事前生成システム設計版