Medusa.js Headless Commerce: Architecture, API Structure, and SEO Implementation
Most ecommerce stores rent their infrastructure from Shopify or BigCommerce and live with the SEO limitations that come with it: forced URL prefixes, 2-3 second minimum page loads from Liquid rendering, and zero control over canonical tags or schema markup. Medusa.js is the open-source, API-first backend that eliminates every one of those constraints. I have built 4 production stores on Medusa since early 2024, and the SEO performance gap versus hosted platforms is not subtle. This guide covers the architecture, the API structure, and the specific SEO implementation patterns I use on every Medusa build.
Table of Contents
Why Medusa.js for Ecommerce (And When It's Wrong)
Medusa.js is an open-source, Node.js-based headless commerce backend that gives you full ownership of your store's data, URL structure, and frontend rendering. It is the platform I reach for whenever a client needs SEO performance that hosted platforms physically cannot deliver. The core trade-off is simple: you invest more time upfront in exchange for zero recurring platform tax and complete technical control.
I started using Medusa in early 2024 after spending 3 years fighting Shopify's Liquid rendering engine, WooCommerce's plugin bloat, and Magento's enterprise complexity. The first store I migrated from Shopify to Medusa + Next.js saw LCP drop from 3.8s to 0.9s and organic sessions increase by 34% over the following 4 months. That was the result of faster page loads, clean URL structures, and complete schema markup control.
But Medusa is wrong for 2 types of stores. If you are a solo founder with no developer access and fewer than 100 SKUs, Shopify will get you to market faster. If you need native B2B features like tiered pricing, quote workflows, and company account hierarchies out of the box, Medusa's B2B capabilities still require custom module development. For everyone else building a serious D2C or multi-region store, Medusa is the strongest open-source option available.
Medusa.js Architecture: How the Pieces Fit Together
Medusa follows an API-first architecture where the backend exposes REST and (as of v2) GraphQL endpoints that any frontend can consume. The backend handles products, orders, customers, payments, fulfillment, and tax calculations. The frontend is completely decoupled. You can use Next.js, Nuxt, Remix, or a mobile app. This separation is what gives you complete SEO control.
The core stack I deploy on every Medusa build
| Layer | Tool | Why This Choice |
|---|---|---|
| Commerce backend | Medusa.js v2 | Open source, Node.js, full API control, native multi-region |
| Frontend | Next.js 15 (App Router) | SSR/SSG/ISR, Metadata API, sub-second page loads |
| Search | Meilisearch | Self-hosted, sub-50ms responses, $0/month vs $500+ for Algolia |
| Payments | Stripe (global), Razorpay (India) | Native Medusa plugins, no additional transaction fees |
| CMS | Sanity or Strapi | Editorial content for blog, landing pages, brand storytelling |
| Resend + React Email | Transactional emails built in React, $0 for first 3,000/month | |
| Hosting | Railway (backend) + Vercel (frontend) | $40-60/month total for backend + frontend + database |
Every piece in this stack is replaceable. If you prefer Nuxt over Next.js, swap the frontend. If you need Paystack for Africa, swap the payment plugin. That is the point of composable commerce: no vendor lock-in on any layer. I covered the frontend side of this stack in detail in our Next.js ecommerce SEO guide.
How data flows from Medusa to the browser
Understanding the data flow is critical for SEO because it determines what Google sees when it crawls your pages. The sequence works like this: a visitor (or Googlebot) requests a URL. Next.js receives the request and makes an API call to your Medusa backend to fetch product, category, or collection data.
Next.js renders the full HTML on the server, including all product content, meta tags, and JSON-LD schema markup. The complete HTML is sent to the browser. No JavaScript execution required for Google to see your content.
With ISR (Incremental Static Regeneration), this flow is even faster. Next.js generates the HTML once, caches it on the CDN, and serves it instantly on subsequent requests. When product data changes in Medusa (price update, new variant, inventory change), a webhook triggers on-demand revalidation of that specific page. The cached HTML updates within seconds.
API Structure and Data Modeling for SEO
Medusa's API returns structured product data that maps directly to what you need for SEO: titles, descriptions, handles (slugs), images with alt text, variant pricing, and inventory status. The API is split into two surfaces: the Store API (public, for your frontend) and the Admin API (authenticated, for your admin panel and integrations).
Product data structure
The GET /store/products/:id endpoint returns a product object with every field you need for a fully optimized product page. Here is the shape of the response and how each field maps to an SEO element:
// Medusa Store API response → SEO mapping
{
"product": {
"title": "Vitamin C Brightening Serum", // → <title> tag, og:title, Product schema name
"handle": "vitamin-c-brightening-serum", // → URL slug: /products/vitamin-c-brightening-serum
"description": "20% Vitamin C serum...", // → meta description, Product schema description
"thumbnail": "https://cdn.../front.webp", // → og:image, Product schema image
"images": [ // → Product image gallery, image sitemap
{ "url": "https://cdn.../front.webp" },
{ "url": "https://cdn.../texture.webp" }
],
"variants": [
{
"title": "30ml",
"prices": [
{ "amount": 2800, "currency_code": "usd" }, // → Offer schema price
{ "amount": 2200, "currency_code": "eur" } // → Multi-currency Offer
],
"inventory_quantity": 142 // → Offer schema availability
}
],
"collection": {
"title": "Serums", // → BreadcrumbList schema
"handle": "serums" // → Category URL segment
},
"tags": [ // → Related keywords, internal linking signals
{ "value": "vitamin-c" },
{ "value": "brightening" }
],
"metadata": { // → Custom SEO fields
"seo_title": "Vitamin C Serum | GlowBase - Free Shipping",
"seo_description": "Shop our 20% Vitamin C serum..."
}
}
}The metadata field is where I store custom SEO data that does not fit into Medusa's default product fields. SEO titles, meta descriptions, canonical overrides, and noindex flags all go in metadata. You can add these fields to the Medusa admin panel using a custom widget, or manage them through a headless CMS like Sanity that connects to your product catalog.
Custom SEO fields through Medusa modules
For stores where the marketing team needs fine-grained SEO control, I build a custom Medusa module that adds dedicated SEO fields to every product and collection. This is cleaner than stuffing everything into the metadata JSON blob. The module adds fields for seo_title, seo_description, canonical_url, robots_directive, and schema_overrides. These fields appear in the admin panel as a dedicated "SEO" tab on every product editor.
Next.js Frontend: The SEO Layer
The Next.js frontend is where all your SEO work materializes. Medusa provides the data. Next.js turns it into server-rendered HTML with proper meta tags, schema markup, sitemaps, and sub-second page loads. Here is the product page implementation pattern I use on every build.
// app/products/[handle]/page.tsx
import { Metadata } from 'next'
import { medusa } from '@/lib/medusa-client'
export async function generateStaticParams() {
const { products } = await medusa.products.list({ limit: 1000 })
return products.map((p) => ({ handle: p.handle }))
}
export async function generateMetadata({
params,
}: {
params: { handle: string }
}): Promise<Metadata> {
const { product } = await medusa.products.retrieve(params.handle)
const seoTitle = product.metadata?.seo_title || product.title
const seoDesc = product.metadata?.seo_description || product.description
return {
title: `${seoTitle} | YourStore`,
description: seoDesc,
alternates: {
canonical: `/products/${product.handle}`,
},
openGraph: {
title: seoTitle,
description: seoDesc,
images: [{ url: product.thumbnail, width: 1200, height: 630 }],
},
}
}
export const revalidate = 3600 // ISR: revalidate every hour
export default async function ProductPage({
params,
}: {
params: { handle: string }
}) {
const { product } = await medusa.products.retrieve(params.handle)
const lowestPrice = Math.min(
...product.variants.flatMap((v) =>
v.prices.filter((p) => p.currency_code === 'usd').map((p) => p.amount)
)
)
return (
<>
<script
type="application/ld+json"
dangerouslySetInnerHTML={{
__html: JSON.stringify({
'@context': 'https://schema.org',
'@type': 'Product',
name: product.title,
description: product.description,
image: product.images.map((i) => i.url),
brand: { '@type': 'Brand', name: 'YourStore' },
offers: {
'@type': 'Offer',
priceCurrency: 'USD',
price: (lowestPrice / 100).toFixed(2),
availability: 'https://schema.org/InStock',
},
}),
}}
/>
{/* Product page component */}
</>
)
}This pattern gives you static generation for all product pages at build time, hourly revalidation through ISR, dynamic metadata per product, and Product schema in the initial HTML. Googlebot receives everything it needs in a single HTTP response with zero JavaScript execution. I covered the full rendering strategy (SSR vs SSG vs ISR) and its SEO implications in our Next.js ecommerce SEO guide.
Sitemap generation from Medusa data
Your sitemap pulls directly from the Medusa API. Create a sitemap.ts file in your Next.js app directory that fetches all products and collections, then returns them as sitemap entries. For stores with 5,000+ products, use generateSitemaps to split into multiple sitemaps of 50,000 URLs each. Every product URL, collection URL, and content page URL goes into the sitemap with accurate lastModified timestamps pulled from Medusa's updated_at fields.
URL Structure You Actually Own
URL structure is where Medusa's advantage over Shopify is most obvious. Shopify forces /products/slug and /collections/slug prefixes on every URL. You cannot remove them. You cannot change them. With Medusa + Next.js, your URL structure is whatever you define in your app directory.
URL patterns I use on Medusa builds
| Page Type | URL Pattern | Shopify Equivalent | SEO Benefit |
|---|---|---|---|
| Product | /vitamin-c-serum | /products/vitamin-c-serum | Shorter URL, keyword closer to root |
| Category | /skincare/serums | /collections/serums | Hierarchical, shows category relationship |
| Brand | /brands/glowbase | Not natively supported | Captures brand + category searches |
| Filtered | /skincare/serums/vitamin-c | Query params only | Indexable filter pages for high-volume terms |
| Blog | /blog/ingredient-guide | /blogs/news/ingredient-guide | Cleaner path, no forced "news" folder |
The ability to put product pages at the root level (/vitamin-c-serum instead of /products/vitamin-c-serum) is a real ranking advantage for competitive keywords. Google treats URL depth as a minor signal, and shorter URLs tend to earn higher click-through rates in search results. I covered the full reasoning and implementation in our ecommerce URL structure guide.
Canonical tag control
On Shopify, canonical tags are generated by the Liquid engine and you have limited override capability. On Medusa + Next.js, you set canonicals explicitly through the generateMetadata function. Every product variant, every filtered category page, and every paginated collection gets exactly the canonical tag you specify. No platform defaults overriding your decisions.
For product variants that share a single page (different sizes or colors), the canonical always points to the base product URL. For paginated category pages, the canonical points to page 1. For filter combinations that match high-volume searches like "vitamin C serums under $30," I create indexable pages with self-referencing canonicals. Filter combos with no search volume get noindexed with canonicals pointing to the parent collection.
Schema Markup Implementation
Schema markup on Medusa stores is implemented in code, not through plugins that generate incomplete markup. You control every field, every nested type, and every conditional property. After implementing complete Product schema (with Offer, AggregateRating, and Brand) across 3 Medusa builds in 2024-2025, the average CTR increase from rich snippets was 28% on product pages that previously had no structured data.
Product schema from Medusa data
The code snippet in Section 4 showed a minimal Product schema. Here is the full implementation I use in production, including AggregateRating, shipping details, and return policy. Every value is pulled dynamically from the Medusa API response and a reviews service. For a thorough walkthrough of every schema type your store needs, read our ecommerce schema markup guide.
- Product name, description, images: Pulled directly from
product.title,product.description, andproduct.images - Offer with price and availability: Computed from the active variant's price in the visitor's currency. Availability is set to
https://schema.org/InStockorhttps://schema.org/OutOfStockbased oninventory_quantity > 0 - AggregateRating: Pulled from your reviews service (Yotpo, Judge.me, or a custom reviews table). Only included when
reviewCount >= 5to avoid thin rating data - Brand: Stored in a custom product metadata field or a dedicated brands collection in your CMS
- BreadcrumbList: Built from the product's collection hierarchy:
Home > Skincare > Serums > Product Name
FAQPage schema on product pages
I add FAQ sections to every product page on Medusa stores. The questions come from 3 sources: the "People Also Ask" box for the product's target keyword, customer support tickets for that product, and the Q&A section on the Amazon listing for the closest competitor product. Each FAQ gets marked up with FAQPage schema. On a beauty store I built in Q3 2024, adding FAQ schema to the top 50 product pages generated an additional 2,100 impressions per week from FAQ rich results within 6 weeks.
Performance: The Numbers Shopify Cannot Match
Page speed is the strongest practical argument for Medusa over hosted platforms. A well-built Medusa + Next.js store serves product pages in 300-600ms on a $20/month VPS. This is not a lab test on a MacBook Pro. This is field data from Chrome User Experience Report (CrUX) across real visitors on real connections.
Real performance benchmarks
Here are the Core Web Vitals numbers from 3 production Medusa stores I built and currently maintain, compared against Shopify stores in the same product category. All measurements are 75th percentile field data from CrUX.
| Metric | Medusa + Next.js (avg) | Shopify (avg) | "Good" Threshold |
|---|---|---|---|
| LCP (product pages) | 0.9s | 3.2s | < 2.5s |
| INP | 85ms | 310ms | < 200ms |
| CLS | 0.02 | 0.14 | < 0.1 |
| TTFB | 120ms | 680ms | < 800ms |
| Total page weight | 280KB | 1.4MB | — |
The performance difference comes from 3 things Shopify physically cannot eliminate. First, Shopify's Liquid rendering engine processes templates on every request, adding 200-400ms to TTFB. Second, Shopify injects mandatory analytics and checkout scripts that add 300-500KB of JavaScript. Third, the average Shopify store has 12-18 third-party app scripts (reviews, upsells, loyalty, chat) that load on every page.
On a Medusa + Next.js build, you start with zero unnecessary scripts and only add what you explicitly choose.
I covered the full diagnostic and optimization process in our ecommerce site speed optimization guide. The short version: if your store is on Shopify and your LCP is above 2.5s, you have hit the platform's performance ceiling. No amount of image compression or app removal will fix the underlying architecture.
Total Cost of Ownership: Medusa vs Shopify vs WooCommerce
The total cost of ownership (TCO) is where Medusa becomes impossible to ignore for growing stores. Shopify's pricing looks affordable until you add transaction fees, app subscriptions, and the opportunity cost of the SEO limitations you are working around.
Monthly TCO at $50K/month revenue
| Cost Category | Medusa + Next.js | Shopify | WooCommerce |
|---|---|---|---|
| Platform/Hosting | $60/mo | $79/mo | $50/mo |
| Transaction fees | $0 (Stripe direct) | $1,450/mo (2.9%) | $0 (Stripe direct) |
| Apps/Plugins | $0 (open source) | $200-500/mo | $50-150/mo |
| Search (Meilisearch vs Algolia) | $0 (self-hosted) | $50-200/mo | $50-200/mo |
| Total monthly | $60 | $1,779-2,229 | $150-400 |
Yes, Medusa has a higher upfront development cost ($5,000-15,000 for a custom build versus $0 to set up Shopify). But at $50K/month revenue, the Shopify stack costs $1,700-2,200/month more than Medusa. The development investment pays for itself in 3-7 months. After that, every month is pure savings.
The hidden cost most people miss: Shopify's SEO limitations cost you organic revenue you never see. When your pages load 2 seconds slower, when your URLs carry unnecessary prefixes, when your schema markup is incomplete because a $29/month app only handles basic Product schema, the revenue impact is invisible but real. Across the 4 stores I have migrated from Shopify to Medusa, the average increase in organic revenue was 22% within 6 months of migration, with page speed and schema improvements as the primary drivers.
FAQ
Medusa.js Headless Commerce FAQ
Medusa.js is not the right choice for every store. If you are pre-revenue, pre-product, or have no developer on the team, start with Shopify and plan your migration path. But if you are past product-market fit, doing $10K+ per month in revenue, and serious about building an organic acquisition channel that compounds, Medusa + Next.js is the stack that gives you full control over every SEO variable that matters.
The performance advantage is measurable. The cost advantage compounds monthly. And the SEO control means your organic growth is limited by your strategy, not by your platform. That is the difference between renting your store and owning it.
Thinking about Medusa.js for your next store?
We audit your current platform's SEO ceiling, map the migration path to Medusa + Next.js, and build the technical SEO foundation from day one. Get a free assessment of what you are leaving on the table.
Aditya went above and beyond to understand our business needs and delivered SEO strategies that actually moved the needle.
Related Articles
Covers SSR, SSG, dynamic routing, metadata API, image optimization, structured data, and performance tuning for Next.js ecommerce frontends.
Master the technical side of ecommerce SEO including crawl budget, indexation, site speed, JavaScript rendering, structured data, and Core Web Vitals.