Bundle: E-commerce SMB Indonesia dengan Medusa + Astro
Stack untuk e-commerce SMB Indonesia: Medusa backend + Astro storefront + Midtrans payment. Lebih murah dari Shopify, lebih scalable dari WooCommerce.
Konteks
Klien e-commerce yang saya handle sejak 2024:
- Toko fashion online (200 SKU, 80-150 order/bulan)
- Toko buku indie (500 SKU, 30-60 order/bulan)
- Toko bibit tanaman (120 SKU, 50-100 order/bulan)
3 dari 3 saya rekomendasi pindah dari Shopify atau WooCommerce ke Medusa. Reason: cost + control.
The stack
Backend (admin + API): Medusa 2.x (Node.js)
Storefront (customer): Astro 6 + React island untuk cart
Database: PostgreSQL 16 (Neon atau self-hosted)
Payment: Midtrans Snap (existing Indonesia integration)
File storage: Cloudflare R2
Image CDN: Cloudflare Images (atau bunny.net)
Email: Resend
Search: Meilisearch (self-hosted) atau Algolia
Hosting:
- Medusa: Hetzner CX21 ($5/bulan)
- Astro storefront: Cloudflare Pages (free)
Mengapa Medusa (bukan Shopify atau WooCommerce)
vs Shopify
| Aspect | Shopify Basic | Medusa self-host |
|---|---|---|
| Monthly cost | $29 ($39 nanti) | $5 (VPS) |
| Transaction fee | 2% (kalau Anda tidak pakai Shopify Payments) | 0% (Anda bayar Midtrans saja 2.5%) |
| Custom development | Plugin/app expensive | Full control |
| Indonesia payment | Stripe-based, butuh workaround | Midtrans native |
| Hosting | Shopify-locked | Anywhere |
Saving untuk klien 100 order/bulan @ Rp 500rb avg:
- Shopify cost: $29 + 2% × Rp 50jt = $29 + $66 = ~Rp 1.5 juta/bulan
- Medusa cost: $5 + (Midtrans 2.5% = $83) = ~Rp 1.4 juta/bulan
- Saving small di volume kecil, tapi scaling lebih murah.
Plus: Medusa adalah open-source, Anda own code.
vs WooCommerce
| Aspect | WooCommerce | Medusa |
|---|---|---|
| Stack | PHP + MySQL + WordPress | Node + Postgres |
| Performance | OK, tapi WP plugin can slow | Faster (modern stack) |
| Headless support | Possible but awkward | Native (API-first) |
| Customization | Plugin + custom theme | Full code-level |
| Hosting | cPanel hosting (Niagahoster, dll) | VPS or PaaS |
WooCommerce winner kalau team Anda WordPress-native. Medusa winner kalau Anda comfortable dengan modern stack + butuh headless.
Setup step
1. Backend (Medusa)
# Setup di Hetzner VPS Singapore
ssh root@your-vps
# Install Node 22 + PostgreSQL
curl -fsSL https://deb.nodesource.com/setup_22.x | bash
apt install -y nodejs postgresql
# Clone Medusa starter
npx create-medusa-app@latest --skip-db --skip-env
cd my-medusa-store
# Configure env
cat > .env <<EOF
DATABASE_URL=postgresql://medusa:password@localhost:5432/medusa_store
REDIS_URL=redis://localhost:6379
COOKIE_SECRET=$(openssl rand -hex 32)
JWT_SECRET=$(openssl rand -hex 32)
ADMIN_CORS=https://admin.tokoanda.id
STORE_CORS=https://tokoanda.id
EOF
# Run migrations + seed
yarn medusa migrations run
yarn seed
# Start dev
yarn dev
Admin UI di http://localhost:9000/admin. Production: setup Nginx + systemd untuk run di background.
2. Midtrans payment integration
Medusa native support Stripe/PayPal. Untuk Midtrans, butuh custom plugin atau API integration via Webhook.
// modules/payment-midtrans/service.ts
import { AbstractPaymentProvider } from '@medusajs/framework/utils';
import midtransClient from 'midtrans-client';
class MidtransPaymentProvider extends AbstractPaymentProvider {
static identifier = 'midtrans';
private snap = new midtransClient.Snap({
isProduction: process.env.NODE_ENV === 'production',
serverKey: process.env.MIDTRANS_SERVER_KEY!,
});
async initiatePayment(context) {
const { amount, currency_code, resource_id } = context;
const transaction = await this.snap.createTransaction({
transaction_details: {
order_id: resource_id,
gross_amount: Math.round(amount), // Midtrans expects IDR integer
},
enabled_payments: ['bca_va', 'bni_va', 'mandiri_va', 'gopay', 'qris'],
});
return {
session_data: {
token: transaction.token,
redirect_url: transaction.redirect_url,
},
};
}
// Webhook handler (separate route)
async authorizePayment(payment, context) {
// Verify signature dari Midtrans webhook
// Update payment status
}
}
3. Storefront (Astro)
---
// src/pages/produk/[handle].astro
import StorefrontLayout from '~/layouts/StorefrontLayout.astro';
import AddToCart from '~/components/AddToCart.jsx'; // React island
export async function getStaticPaths() {
const res = await fetch(`${import.meta.env.MEDUSA_URL}/store/products`);
const { products } = await res.json();
return products.map((p) => ({
params: { handle: p.handle },
props: { product: p },
}));
}
const { product } = Astro.props;
---
<StorefrontLayout title={product.title}>
<article>
<img src={product.thumbnail} alt={product.title} />
<h1>{product.title}</h1>
<p>Rp {product.variants[0].calculated_price.toLocaleString('id-ID')}</p>
<p>{product.description}</p>
<AddToCart productId={product.id} client:visible />
</article>
</StorefrontLayout>
Storefront static (Astro generate semua product page at build). Trigger rebuild saat product update via Medusa webhook.
4. Image handling
Klien upload product image via Medusa admin → Medusa upload ke Cloudflare R2 (S3-compatible). Storefront fetch image via Cloudflare Images variant untuk auto-resize/format.
Original (admin upload): 4000x3000 JPEG, 2MB
↓ (Cloudflare Images)
Thumbnail: 400x300 AVIF, 25KB
Product detail: 1200x900 AVIF, 80KB
Zoom view: 2400x1800 AVIF, 250KB
Cost: Cloudflare Images $5/bulan untuk 100K stored + unlimited delivery. Saving signifikan vs Shopify image bandwidth charge.
Performance untuk customer Indonesia
Bench dari klien fashion (200 SKU):
- Homepage LCP (Jakarta 4G): 1.1s
- Product detail LCP: 1.3s
- Add to cart latency: 180ms
- Checkout to payment redirect: 800ms
Untuk e-commerce kompetitif, ini metric yang win conversion (vs Shopify default 2.5-3.5s LCP).
Common pain point
Inventory sync: kalau klien juga jualan di marketplace (Tokopedia, Shopee), butuh sync inventory manual atau pakai middleware (Shipper, dll). Medusa belum punya official integration untuk Indonesian marketplace.
SEO untuk product page: pastikan setiap product punya unique title, meta description, schema Product markup. Saya generate via Astro frontmatter dari Medusa data.
Customer account: Medusa default punya customer login. Untuk SMB Indonesia, sometimes lebih simple guest checkout (kebanyakan Indonesian customer prefer not login). Configure di Medusa region settings.
Shipping rate: Indonesia shipping (JNE, J&T, SiCepat, Tiki) butuh integration. Pakai RajaOngkir API atau Biteship API untuk live shipping rate.
// Get shipping rate untuk order
const res = await fetch('https://api.biteship.com/v1/rates/couriers', {
headers: { Authorization: `Bearer ${BITESHIP_KEY}` },
// ... courier_code, origin, destination, weight
});
Cost summary
Untuk klien e-commerce 100 order/bulan, 200 SKU:
| Item | Cost (USD) |
|---|---|
| Hetzner CX21 (Medusa backend) | $5 |
| Cloudflare Pages (Astro storefront) | $0 |
| Neon Postgres atau self-hosted | $0-19 |
| Cloudflare R2 + Images | $5 |
| Resend email | $0 |
| Biteship API | $0-50 (per shipment fee) |
| Midtrans fee | 2.5% per transaksi |
| Total fixed | $10-29/bulan |
vs Shopify Basic $29 + 2% transaction fee + apps subscriptions. Medusa saving signifikan di scale.
Verdict
Recommended untuk SMB Indonesia e-commerce yang:
- Bersedia self-host backend (atau hire dev untuk maintain)
- Sudah 50+ order/bulan (di bawah ini, Shopify Basic acceptable)
- Mau control penuh + customization
Skip kalau:
- Anda solo founder tanpa dev skill (Shopify lebih plug-and-play)
- Order volume sangat tinggi (>500/hari) — perlu evaluasi infra serious
- Team Anda WordPress-only — WooCommerce easier transition
Untuk SEO lokal toko fisik yang juga online, prioritaskan: GBP optimization, schema LocalBusiness markup, dan review velocity yang natural. Tools-stack di atas tetap relevan, dengan tambahan local SEO audit setiap kuartal.
Ditulis oleh Asti Larasati