🛒 E-commerce Stack

12 min read
ecommerceshoppaymentinventorynextjsstripeheadless

이커머스 플랫폼을 위한 검증된 기술 스택 - 결제, 재고, 주문 관리 최적화

🛒 E-commerce Stack

이커머스 플랫폼을 위한 검증된 기술 스택 결제, 재고, 주문 관리까지 - 빠르게 시작하고 확장 가능하게


🎯 이 스택이 적합한 경우

✅ 추천하는 경우

  • 커스텀 이커머스 플랫폼 구축
  • Shopify 이상의 커스터마이징 필요
  • 헤드리스 커머스 아키텍처 원함
  • D2C 브랜드 또는 마켓플레이스
  • 국제화 및 다중 통화 지원 필요

❌ 다른 스택을 고려해야 할 경우

  • 빠른 출시가 최우선 → Shopify, WooCommerce
  • 소규모 판매 → Gumroad, Lemon Squeezy
  • 복잡한 B2B 커머스 → Salesforce Commerce

🧩 핵심 구성요소

🖥️ Frontend

Framework:  Next.js 14 (App Router)
Language:   TypeScript
Styling:    Tailwind CSS
Components: shadcn/ui
State:      Zustand (장바구니)
Images:     next/image + Cloudinary

🛍️ Commerce Engine (선택)

| 옵션 | 특징 | 추천 규모 | | ---------------------- | ---------------- | --------- | | Medusa.js | 오픈소스, 풀기능 | 중~대규모 | | Saleor | GraphQL 기반 | 대규모 | | Shopify Storefront | 헤드리스 API | 모든 규모 | | Custom (Prisma) | 완전 제어 | 소규모 |

💳 결제 시스템

Primary:   Stripe (카드, Apple Pay, Google Pay)
Korea:     토스페이먼츠 / 아임포트
Crypto:    Coinbase Commerce (옵션)
Tax:       Stripe Tax / TaxJar

🗄️ Backend & Database

Database:  PostgreSQL (Supabase/PlanetScale)
ORM:       Prisma
Cache:     Redis (Upstash)
Search:    Algolia / Meilisearch
Queue:     Inngest / BullMQ

💰 비용 예상

MVP 단계 (월 주문 100건)

| 항목 | 서비스 | 비용 | | ----------- | ----------------- | ------------------------ | | 호스팅 | Vercel | $0-20 | | DB | Supabase Pro | $25 | | 이미지 | Cloudinary | $0 | | 검색 | Meilisearch Cloud | $0-30 | | 이메일 | Resend | $0-20 | | 결제 수수료 | Stripe 2.9%+30¢ | 변동 | | 합계 | | ~$75/월 + 결제수수료 |

성장 단계 (월 주문 1000건)

| 항목 | 서비스 | 비용 | | -------- | ------------ | ------------------------- | | 호스팅 | Vercel Pro | $20 | | DB | Supabase Pro | $25 | | 이미지 | Cloudinary | $89 | | 검색 | Algolia | $35 | | Redis | Upstash | $10 | | 합계 | | ~$180/월 + 결제수수료 |


🛠️ 프로젝트 구조

ecommerce-app/
├── app/
│   ├── (shop)/              # 쇼핑 페이지
│   │   ├── products/
│   │   │   ├── [slug]/page.tsx
│   │   │   └── page.tsx
│   │   ├── cart/page.tsx
│   │   ├── checkout/page.tsx
│   │   └── orders/page.tsx
│   ├── (admin)/             # 관리자 대시보드
│   │   ├── products/
│   │   ├── orders/
│   │   └── analytics/
│   └── api/
│       ├── webhooks/stripe/
│       └── checkout/
├── components/
│   ├── product/
│   │   ├── ProductCard.tsx
│   │   ├── ProductGallery.tsx
│   │   └── AddToCart.tsx
│   ├── cart/
│   │   ├── CartDrawer.tsx
│   │   └── CartItem.tsx
│   └── checkout/
├── lib/
│   ├── stripe.ts
│   ├── cart-store.ts
│   └── db/
└── prisma/
    └── schema.prisma

🗃️ 데이터 모델

Prisma Schema (핵심)

model Product {
  id          String   @id @default(cuid())
  name        String
  slug        String   @unique
  description String?
  price       Int      // cents로 저장
  comparePrice Int?    // 할인 전 가격
  images      String[] // URL 배열
  inventory   Int      @default(0)
  status      ProductStatus @default(DRAFT)
  categories  Category[]
  variants    ProductVariant[]
  createdAt   DateTime @default(now())
}

model Order {
  id          String   @id @default(cuid())
  orderNumber String   @unique
  userId      String?
  email       String
  status      OrderStatus @default(PENDING)
  items       OrderItem[]
  subtotal    Int
  shipping    Int
  tax         Int
  total       Int
  shippingAddress Json
  createdAt   DateTime @default(now())
}

model OrderItem {
  id        String  @id @default(cuid())
  orderId   String
  order     Order   @relation(fields: [orderId], references: [id])
  productId String
  product   Product @relation(fields: [productId], references: [id])
  quantity  Int
  price     Int     // 주문 시점 가격 저장
}

🛒 장바구니 구현

Zustand 스토어

// lib/cart-store.ts
import { create } from "zustand";
import { persist } from "zustand/middleware";

interface CartItem {
  productId: string;
  quantity: number;
  price: number;
  name: string;
  image: string;
}

interface CartStore {
  items: CartItem[];
  addItem: (item: CartItem) => void;
  removeItem: (productId: string) => void;
  updateQuantity: (productId: string, quantity: number) => void;
  clearCart: () => void;
  total: () => number;
}

export const useCartStore = create<CartStore>()(
  persist(
    (set, get) => ({
      items: [],
      addItem: (item) =>
        set((state) => {
          const existing = state.items.find(
            (i) => i.productId === item.productId,
          );
          if (existing) {
            return {
              items: state.items.map((i) =>
                i.productId === item.productId
                  ? { ...i, quantity: i.quantity + 1 }
                  : i,
              ),
            };
          }
          return { items: [...state.items, { ...item, quantity: 1 }] };
        }),
      // ... other methods
      total: () =>
        get().items.reduce((sum, item) => sum + item.price * item.quantity, 0),
    }),
    { name: "cart-storage" },
  ),
);

💳 결제 플로우

Stripe Checkout (권장)

// app/api/checkout/route.ts
import Stripe from "stripe";

const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!);

export async function POST(req: Request) {
  const { items } = await req.json();

  const session = await stripe.checkout.sessions.create({
    payment_method_types: ["card"],
    line_items: items.map((item: any) => ({
      price_data: {
        currency: "krw",
        product_data: {
          name: item.name,
          images: [item.image],
        },
        unit_amount: item.price,
      },
      quantity: item.quantity,
    })),
    mode: "payment",
    success_url: `${process.env.NEXT_PUBLIC_URL}/orders/success?session_id={CHECKOUT_SESSION_ID}`,
    cancel_url: `${process.env.NEXT_PUBLIC_URL}/cart`,
  });

  return Response.json({ url: session.url });
}

Webhook 처리

// app/api/webhooks/stripe/route.ts
export async function POST(req: Request) {
  const payload = await req.text();
  const sig = req.headers.get("stripe-signature")!;

  const event = stripe.webhooks.constructEvent(
    payload,
    sig,
    process.env.STRIPE_WEBHOOK_SECRET!,
  );

  switch (event.type) {
    case "checkout.session.completed":
      // 주문 생성
      await createOrder(event.data.object);
      break;
    case "payment_intent.succeeded":
      // 결제 완료 처리
      break;
  }

  return Response.json({ received: true });
}

🔍 검색 & 필터링

Meilisearch 설정

// lib/search.ts
import { MeiliSearch } from "meilisearch";

const client = new MeiliSearch({
  host: process.env.MEILISEARCH_HOST!,
  apiKey: process.env.MEILISEARCH_API_KEY,
});

// 상품 인덱싱
export async function indexProducts(products: Product[]) {
  await client.index("products").addDocuments(products);
}

// 검색
export async function searchProducts(query: string, filters?: string) {
  return client.index("products").search(query, {
    filter: filters,
    facets: ["category", "price_range"],
  });
}

📦 재고 관리

재고 차감 로직

// 주문 생성 시 재고 차감 (트랜잭션)
await prisma.$transaction(async (tx) => {
  for (const item of orderItems) {
    const product = await tx.product.update({
      where: { id: item.productId },
      data: {
        inventory: { decrement: item.quantity },
      },
    });

    if (product.inventory < 0) {
      throw new Error(`Insufficient inventory for ${product.name}`);
    }
  }

  await tx.order.create({ data: orderData });
});

🇰🇷 한국 결제 연동

토스페이먼츠

// 토스페이먼츠 결제 위젯 (프론트엔드)
const tossPayments = TossPayments(clientKey);

await tossPayments.requestPayment("카드", {
  amount: 50000,
  orderId: "ORDER_123",
  orderName: "상품명",
  successUrl: `${window.location.origin}/success`,
  failUrl: `${window.location.origin}/fail`,
});

아임포트 (포트원)

// 아임포트 통합 결제
IMP.request_pay(
  {
    pg: "kakaopay",
    pay_method: "card",
    merchant_uid: `order_${Date.now()}`,
    name: "상품명",
    amount: 50000,
    buyer_email: "buyer@example.com",
    buyer_name: "구매자",
    buyer_tel: "010-1234-5678",
  },
  callback,
);

📊 분석 & 추적

이커머스 이벤트

// 상품 조회
analytics.track("Product Viewed", {
  product_id: product.id,
  product_name: product.name,
  price: product.price,
  category: product.category,
});

// 장바구니 추가
analytics.track("Product Added", {
  product_id: product.id,
  quantity: 1,
  cart_id: cartId,
});

// 구매 완료
analytics.track("Order Completed", {
  order_id: order.id,
  total: order.total,
  products: order.items,
});

🚀 추천 스타터

오픈소스 보일러플레이트

SaaS 플랫폼

  • Shopify Storefront API - 가장 안정적
  • BigCommerce - 엔터프라이즈급
  • Swell - 헤드리스 특화

🔗 관련 콘텐츠


이커머스 스택 관련 질문은 Issues에 남겨주세요!

Found this helpful? Share it with others!
Tweet

🔗 Related Content

You might also be interested in these articles

🏗️ stack

🚀 Modern SaaS MVP Stack

4주 안에 SaaS를 출시하기 위한 검증된 기술 스택 - Next.js, Supabase, Stripe로 빠르게 시작하기

8 min read
nextjs, supabase +13
Read more
🌟 awesome

🌟 Awesome Next.js

Next.js 개발자를 위한 최고의 라이브러리, 도구, 리소스 모음

5 min read
awesome, nextjs +4
Read more
🏢 company

🏢 Stripe 기술 스택 분석

스트라이프가 전 세계 수백만 비즈니스의 결제를 처리하는 기술 스택 심층 분석 - Ruby, Go, Java로 구축한 금융 인프라의 미래

33 min read
stripe, ruby +11
Read more
🏢 company

🏢 Vercel 기술 스택 분석

버셀이 프론트엔드 배포의 미래를 만드는 기술 스택 심층 분석 - Next.js, Edge Functions, 그리고 글로벌 엣지 네트워크

28 min read
vercel, nextjs +9
Read more

Found this helpful?

Help us improve this content by contributing on GitHub or sharing your feedback with the community.