
The Tech Stack Behind Such Shop
Such Shop runs on a stack I picked deliberately to keep one Next.js process serving many tenant domains while leaving each tenant's data, payments, and theming completely isolated. This is a quick tour for the curious.
The shape
- Backend: Medusa v2 — open-source headless commerce, modular by design. We use the core product/cart/order modules and write our own modules for the things Medusa doesn't ship: tenant config, themes, blog, custom pages, bookings, and per-tenant notification preferences.
- Storefront: Next.js 15 with the App Router and Server Components. A single deployment handles every tenant; middleware reads the `Host` header on each request, looks up the matching tenant, and injects the right publishable API key into the downstream calls.
- Tenant Admin: A separate Next.js app at `admin.suchshop.lol` so the shop owner has a focused UI without seeing the rest of the platform.
- Database: PostgreSQL. Redis for the event bus and workflow scheduling.
- Storage: MinIO in staging, S3 in production — same protocol, drop-in swap.
Multi-tenant from request to response
The request flow looks like this:
1. Browser hits `suchshop.lol`. 2. Next.js middleware calls the backend's `/tenant/lookup?domain=suchshop.lol` endpoint and gets back the sales-channel ID, the tenant's publishable API key, and a few flags. 3. Middleware caches the lookup for five minutes, sets request headers (`x-medusa-publishable-key`, `x-tenant-id`, etc.), and lets the request continue. 4. Pages that run server-side use those headers to scope every Medusa call to this tenant's catalog. The next tenant's request goes through the same code path with different headers.
`www.suchshop.lol` redirects to the apex via a 301 — both DNS records exist, but the canonical domain is the apex, so we consolidate SEO signals there.
Payments
Card and wallet checkout goes through Stripe Connect for client tenants (the tenant is the merchant of record; Such Software takes a platform fee) or directly through Stripe for our own shops. Crypto goes straight to per-tenant wallets:
- BTCPay Server for BTC/LTC. Each tenant has their own xpub.
- xmrcheckout for Monero, wowcheckout for Wownero, and grin for Grin. Each per-tenant view-only wallet, each with its own webhook back to Medusa.
There's no centralized pooling of crypto funds anywhere in the system. That's a deliberate compliance posture — pooling would put us in money transmitter territory under 18 U.S.C. § 1960. Direct-to-wallet keeps the platform out of that pipe.
What runs the production server
- PM2 keeps the storefront, tenant-admin, Medusa backend, and worker processes online with auto-restart.
- Nginx Proxy Manager terminates TLS and routes `*.suchshop.lol`, `api-staging.such.software`, and the BTCPay/checkout subdomains to the right service.
- Modoboa runs transactional mail with per-tenant sending domains.
What I'm proudest of
- The middleware pipeline that makes multi-tenancy invisible to most of the storefront code.
- A subscriber pattern for shipping volume tiers that lets tenants enter rows like "1–3 prints for $5, 4–6 for $10" with no custom fulfillment provider involved.
- A canonical-URL setup where each tenant's sitemap, OG tags, and JSON-LD all reflect that tenant's domain — even though a single Next.js process generates them all.
The repo is private for now. Happy to walk through specifics if you're building something similar.