OSS Eコマース比較:Medusa.js vs WooCommerce vs Saleor でShopifyを脱却する
オープンソースラボ編集部 ・ 2026年6月14日
OSSEコマース比較:Medusa.js vs WooCommerce vs Saleor でShopifyを脱却する
Shopify(月$79〜)・BigCommerce(月$29〜)・Salesforce Commerce Cloud(年間数百万円〜)に対して、Medusa.js(Next.js時代のOSSコマースエンジン)・WooCommerce(WordPress統合・最大シェア)・Saleor(GraphQL APIファーストOSS)はECをセルフホストできます。
OSS Eコマースを選ぶ理由
- コスト: Shopify Plus(月$2,300〜)→ Medusa.jsセルフホストで0円
- 手数料なし: Shopifyは売上の0.2〜2%の取引手数料。OSS版はなし
- カスタマイズ: 独自決済フロー・会員機能・B2Bワークフローをコードで実装
- データ主権: 顧客の購買履歴・個人情報を自社DBで管理
主要ツールの概要
Medusa.js
2021年公開、TypeScript/Node.js製のOSSです。GitHubスター27k+。Next.js App RouterとAPIファーストで設計されたモダンなHeadless Commerceです。商品管理・注文管理・在庫・支払い・配送・割引・顧客管理をモジュールとして提供し、フロントエンドは自由に選択できます。
# Medusa.js プロジェクトを作成
npx create-medusa-app@latest
# または手動セットアップ
mkdir my-store && cd my-store
npx @medusajs/medusa-cli new . --skip-db
# 環境変数を設定(.env)
cat > .env << 'EOF'
DATABASE_URL=postgresql://postgres:password@localhost:5432/medusa-db
REDIS_URL=redis://localhost:6379
JWT_SECRET=your-super-secret-jwt-key
COOKIE_SECRET=your-cookie-secret
# Stripe決済
STRIPE_API_KEY=sk_live_xxxx
STRIPE_WEBHOOK_SECRET=whsec_xxxx
# メール(SendGrid)
SENDGRID_API_KEY=SG.xxxx
SENDGRID_FROM=orders@yourstore.com
EOF
# DB マイグレーション
npx medusa db:migrate
# 開発サーバー起動
npx medusa develop
// Medusa.js カスタムAPI Route
// src/api/store/custom/route.ts
import { MedusaRequest, MedusaResponse } from '@medusajs/medusa'
import { ProductService } from '@medusajs/medusa'
// B2B向けのバルク価格取得エンドポイント
export const GET = async (req: MedusaRequest, res: MedusaResponse) => {
const productService: ProductService = req.scope.resolve('productService')
const { customer_group_id } = req.query
const products = await productService.list(
{ is_giftcard: false },
{
relations: ['variants', 'variants.prices'],
}
)
// 顧客グループに応じた価格を返す
const productsWithPrice = products.map(product => ({
id: product.id,
title: product.title,
variants: product.variants.map(variant => ({
id: variant.id,
sku: variant.sku,
price: variant.prices?.find(
p => p.price_list?.customer_groups?.some(cg => cg.id === customer_group_id)
)?.amount ?? variant.prices?.[0]?.amount,
})),
}))
return res.json({ products: productsWithPrice })
}
// Next.js App Router で Medusa ストアフロントを構築
// app/products/[handle]/page.tsx
import Medusa from '@medusajs/medusa-js'
const medusa = new Medusa({
baseUrl: process.env.NEXT_PUBLIC_MEDUSA_BACKEND_URL!,
maxRetries: 3,
})
interface ProductPageProps {
params: { handle: string }
}
export default async function ProductPage({ params }: ProductPageProps) {
// サーバーサイドで商品データを取得(ISR対応)
const { products } = await medusa.products.list({
handle: params.handle,
expand: 'variants,variants.prices,images,collection,tags',
})
const product = products[0]
if (!product) return <div>商品が見つかりません</div>
return (
<div className="max-w-4xl mx-auto p-8">
<h1 className="text-3xl font-bold">{product.title}</h1>
<p className="text-gray-600 mt-2">{product.description}</p>
<div className="mt-6 grid grid-cols-2 gap-4">
{product.images?.map(img => (
<img key={img.id} src={img.url} alt={product.title} className="rounded-lg" />
))}
</div>
<div className="mt-8">
{product.variants.map(variant => (
<div key={variant.id} className="flex justify-between items-center py-2 border-b">
<span>{variant.title}</span>
<span className="font-bold">
¥{((variant.prices?.[0]?.amount ?? 0) / 100).toLocaleString()}
</span>
</div>
))}
</div>
</div>
)
}
export async function generateStaticParams() {
const { products } = await medusa.products.list({ limit: 100 })
return products.map(p => ({ handle: p.handle }))
}
export const revalidate = 60 // 60秒ごとに再生成
# Medusa Admin API でバックオフィス処理を自動化(Python)
import requests
import os
MEDUSA_URL = 'https://admin.yourstore.com'
def get_admin_token() -> str:
resp = requests.post(f'{MEDUSA_URL}/admin/auth/token', json={
'email': os.environ['MEDUSA_ADMIN_EMAIL'],
'password': os.environ['MEDUSA_ADMIN_PASSWORD'],
})
resp.raise_for_status()
return resp.json()['access_token']
token = get_admin_token()
headers = {'Authorization': f'Bearer {token}'}
# 注文一覧を取得(未処理の注文のみ)
def get_pending_orders():
resp = requests.get(
f'{MEDUSA_URL}/admin/orders',
headers=headers,
params={'fulfillment_status': 'not_fulfilled', 'limit': 50},
)
return resp.json()['orders']
# 在庫を一括更新(CSVからインポート)
def update_inventory(variant_id: str, quantity: int, location_id: str):
resp = requests.post(
f'{MEDUSA_URL}/admin/inventory-items/{variant_id}/location-levels',
headers=headers,
json={'location_id': location_id, 'stocked_quantity': quantity},
)
resp.raise_for_status()
return resp.json()
# 使用例: 倉庫システムから在庫データを同期
import csv
with open('inventory.csv') as f:
for row in csv.DictReader(f):
update_inventory(row['variant_id'], int(row['quantity']), 'loc_main')
print('在庫同期完了')
Saleor
2012年公開、Python/Django製のOSSです。GitHubスター21k+。GraphQL APIのみで設計されたHeadless Commerceで、PWA・React Native・Next.jsなど任意のフロントエンドと組み合わせられます。多通貨・多言語・複数チャンネル(実店舗/ECサイト/モバイルアプリ)を単一バックエンドで管理できます。
# docker-compose.yml - Saleor Commerce
version: '3.8'
services:
api:
image: ghcr.io/saleor/saleor:3.20.0
restart: unless-stopped
ports:
- "8000:8000"
environment:
DATABASE_URL: postgres://saleor:saleor@db/saleor
DEFAULT_FROM_EMAIL: noreply@yourstore.com
SECRET_KEY: ${DJANGO_SECRET_KEY}
ALLOWED_HOSTS: "api.yourstore.com,localhost"
ENABLE_DEBUG_TOOLBAR: "false"
DEFAULT_CHANNEL_SLUG: "jp"
depends_on:
- db
- redis
db:
image: postgres:16-alpine
environment:
POSTGRES_DB: saleor
POSTGRES_USER: saleor
POSTGRES_PASSWORD: saleor
volumes:
- db_data:/var/lib/postgresql/data
redis:
image: redis:7-alpine
volumes:
db_data:
機能比較表
| 比較項目 | Medusa.js | WooCommerce | Saleor |
|---|---|---|---|
| 技術スタック | TypeScript/Node.js | PHP/WordPress | Python/Django |
| GraphQL API | ✅ | ❌(REST) | ✅(フルGraphQL) |
| Headless | ✅ | △ | ✅ |
| B2B機能 | ✅ | △ | ✅ |
| 日本語対応 | ✅ | ✅ | ✅ |
| GitHub Stars | 27k+ | N/A(WP) | 21k+ |
ECサイトはLow-Codeカテゴリ/categories/low-codeのN8N・Zapierと連携して注文→在庫更新→配送指示→メール通知の自動化ワークフローを構築します。マーケティングカテゴリ/categories/marketingのListmonk・PostHogと組み合わせてカート離脱メール・購買分析ダッシュボードを構築する構成も一般的です。
FAQ
Q. ShopifyからMedusa.jsへの移行ステップは?
A. ①データエクスポート: Shopify管理→Settings→Data Export→商品・注文・顧客をCSVで出力②Medusa環境構築: create-medusa-appでバックエンド+Next.jsストアフロントを作成③データインポート: medusa seedコマンドまたはAdmin APIのPOST /admin/products・/admin/customersでCSVデータを移行④決済・配送設定: Stripeプラグイン(@medusajs/payment-stripe)・配送プロバイダー設定⑤ドメイン切り替え: DNSをMedusaサーバーに向けてShopifyのDNSを解除。注意: ShopifyのURLリダイレクト(/products/xxx→Medusaの/products/[handle])を適切に設定してSEOを維持することが重要です。
Q. WooCommerceとMedusa.jsのどちらが日本の中小ECに向いていますか?
A. 既存WordPressサイトがある・非エンジニアが管理するならWooCommerce、Next.js・TypeScriptでカスタマイズしたいエンジニアチームにはMedusa.jsが向いています。WooCommerce: ①WordPress管理画面で商品・注文管理(ノーコード)②日本決済(PAY.JP・KOMOJU・SBペイメント)の公式プラグインが充実③送料計算・消費税計算が日本向けに整備されたプラグインあり。Medusa.js: ①TypeScript型安全・Next.js App RouterでSEO最適化が容易②Stripeが主要決済(PAY.JP統合は自作が必要)③消費税(10%・軽減税率8%)の処理は自前実装が必要。2024〜2026年のトレンドはMedusa.jsが急速に日本コミュニティを獲得しています。
Q. Medusa.jsでサブスクリプション機能を実装するには?
A. ①Medusa.js + Stripe Subscriptionsの組み合わせが標準的なアプローチです。実装手順: ①Stripe Dashboardでサブスクリプション商品(Product)とPrice(月額・年額)を作成②Medusa.jsのカスタムAPIエンドポイント(src/api/store/subscribe)でStripe Subscription作成③Stripe WebhookでMedusaに支払い状況を通知(customer.subscription.updatedイベント)④Medusaのカスタマーテーブルにsubscription_statusフィールドを追加してアクセス制御。またはmedusa-plugin-subscription(コミュニティプラグイン)を使えばベース実装が30分で完了します。
Q. SaleorのGraphQL APIはどう使うのですか?
A. SaleorはすべてのEC操作をGraphQL(Strawberry Python)で提供します。エンドポイント: https://api.yourstore.com/graphql/。基本クエリ例: 商品一覧はquery { products(channel: "jp") { edges { node { id name slug pricing { priceRange { start { gross { amount currency } } } } } } } }。ミューテーション例: カートに商品を追加はmutation { checkoutLinesAdd(checkoutId: "...", lines: [{variantId: "...", quantity: 1}]) { checkout { id totalPrice { gross { amount } } } } }。TypeScript/React向けのSDK(@saleor/sdk)を使うとGraphQLクエリのtype-safeな実装が容易になります。
まとめ
| ユースケース | 推奨ツール |
|---|---|
| モダンHeadless・TypeScript・B2B | Medusa.js |
| WordPress統合・非エンジニア管理 | WooCommerce |
| GraphQLファースト・多言語多通貨 | Saleor |