비디오 커머스·쇼퍼블 비디오: 영상에서 바로 구매하는 경험 설계

이커머스

비디오 커머스쇼퍼블 비디오콘텐츠 커머스라이브 커머스쇼트폼

이 글은 누구를 위한 것인가

  • 유튜브, 인스타그램 릴스처럼 영상에서 바로 구매 가능하게 만들고 싶은 팀
  • 상품 리뷰 영상과 구매를 연결하려는 이커머스 콘텐츠 팀
  • 쇼퍼블 비디오 플레이어를 직접 개발해야 하는 프론트엔드 개발자

들어가며

영상은 상품의 실제 사용감을 가장 잘 전달한다. 하지만 "영상 보다가 구매하려고 하면 어떻게 사지?" — 이 마찰을 제거하는 것이 쇼퍼블 비디오의 핵심이다.

이 글은 bluefoxdev.kr의 콘텐츠 커머스 전략 을 참고하여 작성했습니다.


1. 쇼퍼블 비디오 구현 방식

[구현 방식 비교]

방식 1: 타임라인 태그 (timestamp → 상품)
  ├── 영상 편집자가 타임스탬프별 상품 태깅
  ├── 플레이어가 현재 시간에 맞는 상품 표시
  └── 예: 0:30에 빨간 재킷 → 하단에 상품 카드 노출

방식 2: 영상 위 클릭 영역 (핫스팟)
  ├── 영상 위에 클릭 가능한 영역 설정
  ├── 재킷 위치에 클릭하면 상품 팝업
  └── 제작 비용 높음, 모바일에서 어려움

방식 3: AI 자동 태깅
  ├── 영상 분석 AI가 상품 자동 인식
  ├── 제품 카탈로그와 매칭
  └── 구현 복잡, 정확도 70~80%

[권장 구현: 방식 1 + 방식 3 혼합]
  편집자가 중요 장면만 수동 태깅 + AI 보조

2. 쇼퍼블 비디오 플레이어

import { useRef, useState, useEffect } from 'react';

interface ProductTag {
  timestampStart: number;  // 초
  timestampEnd: number;
  productId: string;
  productName: string;
  price: number;
  imageUrl: string;
}

interface ShoppableVideoProps {
  videoUrl: string;
  productTags: ProductTag[];
}

function ShoppableVideo({ videoUrl, productTags }: ShoppableVideoProps) {
  const videoRef = useRef<HTMLVideoElement>(null);
  const [currentTime, setCurrentTime] = useState(0);
  const [activeProducts, setActiveProducts] = useState<ProductTag[]>([]);

  useEffect(() => {
    const video = videoRef.current;
    if (!video) return;

    const handleTimeUpdate = () => {
      const t = video.currentTime;
      setCurrentTime(t);
      setActiveProducts(
        productTags.filter(tag => t >= tag.timestampStart && t <= tag.timestampEnd)
      );
    };

    video.addEventListener('timeupdate', handleTimeUpdate);
    return () => video.removeEventListener('timeupdate', handleTimeUpdate);
  }, [productTags]);

  return (
    <div className="relative w-full">
      <video
        ref={videoRef}
        src={videoUrl}
        controls
        className="w-full rounded-lg"
      />

      {/* 상품 오버레이 */}
      {activeProducts.length > 0 && (
        <div className="absolute bottom-16 left-4 right-4 flex gap-3 overflow-x-auto">
          {activeProducts.map(product => (
            <ProductCard key={product.productId} product={product} />
          ))}
        </div>
      )}
    </div>
  );
}

function ProductCard({ product }: { product: ProductTag }) {
  const [added, setAdded] = useState(false);

  return (
    <div className="flex-shrink-0 bg-white/90 backdrop-blur rounded-xl p-3 flex items-center gap-3 shadow-lg min-w-[200px]">
      <img
        src={product.imageUrl}
        alt={product.productName}
        className="w-12 h-12 object-cover rounded-lg"
      />
      <div className="flex-1 min-w-0">
        <p className="text-sm font-medium truncate">{product.productName}</p>
        <p className="text-sm text-gray-600">{product.price.toLocaleString()}원</p>
      </div>
      <button
        onClick={() => { addToCart(product.productId); setAdded(true); }}
        className={`text-xs px-3 py-1.5 rounded-full font-medium ${
          added ? 'bg-green-100 text-green-700' : 'bg-blue-600 text-white'
        }`}
      >
        {added ? '담김' : '담기'}
      </button>
    </div>
  );
}

3. 타임라인 태깅 관리 도구 (어드민)

// 비디오 태그 관리 API
interface VideoTagRequest {
  videoId: string;
  tags: {
    timestampStart: number;
    timestampEnd: number;
    productId: string;
  }[];
}

async function saveVideoTags(req: VideoTagRequest): Promise<void> {
  await db.transaction(async (trx) => {
    // 기존 태그 삭제
    await trx.delete('video_product_tags').where({ video_id: req.videoId });
    
    // 새 태그 삽입
    if (req.tags.length > 0) {
      await trx.insert('video_product_tags').values(
        req.tags.map(tag => ({
          video_id: req.videoId,
          product_id: tag.productId,
          timestamp_start: tag.timestampStart,
          timestamp_end: tag.timestampEnd,
        }))
      );
    }
  });
}

마무리

쇼퍼블 비디오의 핵심은 "보는 것에서 사는 것으로" 마찰을 줄이는 것이다. 타임라인 기반 태깅은 구현이 단순하면서도 효과가 크다. 비디오 중 구매 버튼을 클릭한 사용자는 영상 없이 상품을 발견한 사용자보다 구매율이 훨씬 높다. 콘텐츠팀과 협업하여 주요 상품을 영상에 자연스럽게 노출하는 것이 전략의 시작이다.