Back to blog
MedusaJSNext.jsPostgreSQLE-commerce

What Building a MedusaJS Marketplace Taught Me About Headless Commerce

April 15, 20263 min readby Niloy Mahmud Apu

My first real full-stack responsibility was building an e-commerce marketplace at TrendyDice on MedusaJS — a headless, Node.js commerce engine — with a Next.js storefront and PostgreSQL underneath. I went in thinking commerce was "products and a cart." It is not.

Here's what actually mattered.

Headless means you own the storefront — all of it

With a hosted platform like Shopify, the store is the product. With Medusa, the backend gives you a commerce API and admin, and you build the customer-facing store yourself. That's the whole trade:

  • You gain total control of UX, performance, and how the catalog is presented.
  • You owe everything a hosted platform gives you for free — SEO, page speed, accessibility, checkout flow, the lot.

For a marketplace with non-standard requirements, that control is worth it. For a simple shop, it can be more rope than you need. Know which one you're building before you commit.

Regions and currencies are not an afterthought

The first thing that humbled me: in Medusa, a price isn't a number on a product. Pricing lives inside regions — a region ties together currency, tax rules, and the payment/shipping providers available. A product has prices per region.

If you model your storefront as "product has a price," you'll rewrite it the moment a second currency shows up. Build region-awareness in from line one, even if you launch with a single region.

Extend the data model deliberately

Medusa ships with a sensible commerce schema, but a marketplace always needs more — vendor ownership, commission rules, per-seller metadata. Two options:

  1. metadata fields for small, flexible additions. Fast, but unindexed and easy to abuse.
  2. Custom entities / modules for anything you query or report on.

The rule I settled on: if I ever need to filter or join on it, it gets a real column, not a metadata blob. Metadata is for data you only ever read back attached to its parent.

The storefront lives and dies by caching

A commerce catalog is mostly read-heavy data that changes slowly — perfect for Next.js. Static-render or cache the catalog and product pages, and keep only the genuinely dynamic bits (cart, stock, the customer's account) on the client or freshly fetched.

// Product pages change rarely — render them statically and revalidate.
export const revalidate = 3600; // re-check hourly

export async function generateStaticParams() {
  const products = await getAllProductHandles();
  return products.map((handle) => ({ handle }));
}

The difference between "fetch the catalog on every request" and "serve it from cache and revalidate" is the difference between a store that feels instant and one that feels like work.

Email and the boring flows are the real product

The features customers remember aren't the flashy ones — they're order confirmations that actually arrive, password resets that work, and a checkout that doesn't lose the cart on a flaky connection. I spent a meaningful chunk of time on email integration and edge cases in the order lifecycle, and it mattered more than any UI polish.

The takeaway

Headless commerce hands you a powerful engine and a blank storefront. The engine is the easy part. The lasting lessons were about modeling money correctly (regions first), extending the schema with intent, caching aggressively, and treating the unglamorous flows — email, checkout resilience — as the actual product. That's where a marketplace is won.