Next.js + Medusa.js + Meilisearch: The Modern Ecommerce Tech Stack That Replaces $2,000/Month in SaaS
I built this stack because I was tired of paying $2,000/month in SaaS fees for tools that gave me less control than a $40/month VPS. Next.js for the frontend, Medusa.js for the commerce backend, Meilisearch for product search. Three open-source tools that, combined, outperform Shopify + Algolia on every metric I care about: page speed, SEO control, search relevance, and total cost of ownership. This is the architecture I run for my own projects and recommend to every ecommerce operator who has outgrown their platform.
Table of Contents
1. Why This Stack Exists
The modern ecommerce tech stack I am about to walk you through exists because SaaS platforms charge you a recurring tax on your own revenue while giving you a fraction of the control you need. A Shopify store doing $50K/month pays $79 for the plan, $1,450 in transaction fees (2.9%), $200-500 in apps (reviews, upsells, email, SEO tools), and $200+ for a premium theme. That is $1,930-2,230 every month for a platform that will not let you customize your URL structure, caps your page speed at 3+ seconds due to mandatory platform JavaScript, and locks your data inside their walled garden.
I hit this wall with a D2C beauty client in 2024. Their Shopify store was doing $80K/month but bleeding $2,800/month in platform and app costs. Their LCP was stuck at 3.4 seconds no matter what we optimized because Shopify's Liquid rendering engine and mandatory analytics scripts create a performance floor you cannot break through. Their search was powered by a $480/month Algolia integration that returned irrelevant results for ingredient-based queries.
We rebuilt on Next.js + Medusa.js + Meilisearch. Infrastructure cost dropped to $60/month. LCP dropped to 1.2 seconds. Search relevance for ingredient queries improved from 34% accuracy to 91%. The migration paid for itself in 6 weeks through eliminated SaaS fees alone, before counting the conversion rate lift from faster pages.
This is not a theoretical architecture. I run it, I maintain it, and I have watched it outperform every hosted platform I have worked with across the metrics that actually matter for ecommerce growth. Here is how every piece works and how they connect.
2. Architecture Overview: How the Three Pieces Connect
The architecture follows a clean separation of concerns: Medusa.js handles commerce logic (products, orders, customers, payments), Next.js renders the storefront and handles SEO, and Meilisearch powers product search and filtering. Each component communicates through REST APIs, and each one can be replaced independently without touching the others.
This is the core of composable commerce and MACH architecture applied to a real, production-ready stack. Not the buzzword version that enterprise consultants sell for $50K. The practical version that runs on a $40 VPS.
The data flow works like this: Medusa.js is the source of truth for all product data. When a product is created or updated in the Medusa admin, a webhook fires to sync that data to Meilisearch's search index. Next.js fetches product data from Medusa's REST API for page rendering (using ISR for sub-second page loads) and queries Meilisearch directly for search and filtering. The user's browser never talks to Medusa or Meilisearch directly.
- Medusa.js (Port 9000): Commerce API, admin dashboard, order management, payment processing
- Meilisearch (Port 7700): Search index, typo-tolerant queries, faceted filtering, sub-50ms responses
- Next.js (Port 3000 / Vercel): Server-rendered storefront, ISR product pages, SEO metadata, structured data
- PostgreSQL: Medusa's database for products, orders, customers
- Redis: Session storage and caching layer for Medusa
3. Medusa.js: The Commerce Backend You Actually Own
Medusa.js is an open-source, Node.js-based commerce backend that gives you everything Shopify does, without the monthly tax or the technical ceiling. Products, variants, collections, orders, customers, discounts, gift cards, payments, fulfillment, tax calculation, multi-currency, multi-region. All through a REST API and an admin dashboard that your operations team can use without touching code.
The architecture decision that makes Medusa the right choice over Saleor, Vendure, or other open-source alternatives comes down to three factors. First, it is Node.js/TypeScript end-to-end, which means your entire stack runs one language. Second, its plugin system is genuinely modular. You install a Stripe plugin for payments, a SendGrid plugin for email, and each one is a self-contained npm package. Third, the admin dashboard ships out of the box and is production-ready.
From an SEO perspective, Medusa gives you what Shopify never will: complete control over your data model. You define your product slugs. You control which fields are exposed through the API. You can add custom attributes (ingredient lists, skin type compatibility, application instructions) that feed directly into your Next.js frontend for structured data and on-page SEO content. No app required, no monthly fee, no limitations on schema complexity.
Setting Up Medusa for Production
A production Medusa setup requires a VPS with at least 2GB RAM (4GB recommended for stores with 5,000+ SKUs), PostgreSQL 14+, and Redis 7+. I run all three on a single Hetzner CX31 instance at $15.90/month for stores under 10,000 SKUs. The Medusa server itself consumes roughly 200-400MB of RAM at idle, PostgreSQL takes 300-500MB, and Redis uses 50-100MB. This leaves headroom for traffic spikes.
# Install Medusa CLI and create a new project
npx create-medusa-app@latest my-store
# Project structure after scaffolding:
# my-store/
# backend/ <- Medusa server (Node.js)
# storefront/ <- Next.js frontend (we replace this)
#
# Configure environment variables
# backend/.env
DATABASE_URL=postgresql://user:pass@localhost:5432/medusa
REDIS_URL=redis://localhost:6379
JWT_SECRET=your-jwt-secret
COOKIE_SECRET=your-cookie-secret
STORE_CORS=http://localhost:3000
ADMIN_CORS=http://localhost:7001
# Start the backend
cd my-store/backend
medusa develop4. Next.js: The Frontend That Google Loves
Next.js is the frontend layer that turns Medusa's API data into server-rendered HTML that search engines can crawl, index, and rank. The reason I chose Next.js over Remix, Nuxt, or SvelteKit is not that those frameworks are inferior. It is that Next.js has the largest ecommerce-specific tooling, the most mature deployment infrastructure (Vercel), and Incremental Static Regeneration for product pages that gives you static-site speed with dynamic-site freshness.
For site speed optimization, ISR is the single most important architectural decision in this stack. Every product page is statically generated at build time and served from a CDN edge node. When a product price changes in Medusa, a webhook triggers on-demand revalidation, and the next visitor gets the updated page without any perceptible delay. TTFB on ISR pages is consistently under 100ms globally because the HTML is pre-built and cached at the edge.
Server Components in Next.js App Router are the other critical SEO win. Your product page component runs entirely on the server. The browser receives fully-rendered HTML with all product titles, descriptions, prices, and schema markup present in the initial response. Googlebot sees your complete page without executing JavaScript. For stores with 5,000+ product pages, this eliminates the rendering budget problem that headless commerce SEO guides warn about.
Product Page Architecture in Next.js
The product page is the revenue center of your store. Here is the exact architecture I use for the dynamic product route that fetches from Medusa and generates SEO metadata:
// app/products/[slug]/page.tsx
import { Metadata } from 'next'
import { notFound } from 'next/navigation'
// ISR: revalidate every 60 minutes, on-demand via webhook
export const revalidate = 3600
// Pre-generate all product pages at build time
export async function generateStaticParams() {
const { products } = await fetch(
`${process.env.MEDUSA_URL}/store/products?limit=1000`,
{ headers: { 'x-publishable-api-key': process.env.MEDUSA_KEY! } }
).then(r => r.json())
return products.map((p: { handle: string }) => ({
slug: p.handle,
}))
}
// Dynamic SEO metadata from Medusa product data
export async function generateMetadata({
params,
}: {
params: Promise<{ slug: string }>
}): Promise<Metadata> {
const { slug } = await params
const { products } = await fetch(
`${process.env.MEDUSA_URL}/store/products?handle=${slug}`,
{ headers: { 'x-publishable-api-key': process.env.MEDUSA_KEY! } }
).then(r => r.json())
if (!products?.length) return {}
const product = products[0]
return {
title: `${product.title} | YourStore`,
description: product.description?.slice(0, 155),
alternates: { canonical: `/products/${slug}` },
openGraph: {
title: product.title,
description: product.description,
images: [{ url: product.thumbnail, width: 1200, height: 630 }],
},
}
}
export default async function ProductPage({
params,
}: {
params: Promise<{ slug: string }>
}) {
const { slug } = await params
const { products } = await fetch(
`${process.env.MEDUSA_URL}/store/products?handle=${slug}`,
{ headers: { 'x-publishable-api-key': process.env.MEDUSA_KEY! },
next: { revalidate: 3600 } }
).then(r => r.json())
if (!products?.length) notFound()
const product = products[0]
return (
<>
<ProductJsonLd product={product} />
<ProductTemplate product={product} />
</>
)
}5. Meilisearch: $0/Month Search That Beats Algolia
Meilisearch replaces the $500-2,000/month Algolia bill with a self-hosted search engine that returns results in under 50ms, handles typos out of the box, and supports faceted filtering for every product attribute in your catalog. I have run it on stores with 50,000+ SKUs and the response time stays under 30ms. The entire binary is 50MB and runs on 256MB of RAM for catalogs under 100,000 documents.
What convinced me to switch from Algolia was not just cost. It was search relevance for niche ecommerce queries. On a beauty store, customers search for "niacinamide serum for oily skin" not "serum SKU-4532." Meilisearch's ranking rules let you prioritize matches in the product title, then ingredients, then description, then tags. You define the hierarchy once, and every search query respects it. Algolia can do this too, but the configuration is buried behind a $249/month plan and requires their dashboard to manage.
The typo tolerance is genuinely good. A customer typing "hyalronic acid" instead of "hyaluronic acid" gets the right results on the first keystroke. This is critical for beauty, supplements, and any vertical where product names include scientific or technical terms that buyers frequently misspell.
Syncing Medusa Products to Meilisearch
The sync between Medusa and Meilisearch happens through a subscriber that listens for product events. When a product is created, updated, or deleted in Medusa, the subscriber pushes the change to Meilisearch's index. Initial sync for 5,000 products takes about 3 seconds. Incremental updates are near-instant.
// medusa-config.ts - Register the Meilisearch plugin
module.exports = {
plugins: [
{
resolve: 'medusa-plugin-meilisearch',
options: {
config: {
host: process.env.MEILISEARCH_HOST || 'http://127.0.0.1:7700',
apiKey: process.env.MEILISEARCH_API_KEY,
},
settings: {
products: {
indexSettings: {
searchableAttributes: [
'title',
'description',
'variant_sku',
'collection_title',
],
filterableAttributes: [
'collection_id',
'type_value',
'tags',
'variant_prices.amount',
],
sortableAttributes: [
'created_at',
'variant_prices.amount',
],
rankingRules: [
'words',
'typo',
'proximity',
'attribute',
'sort',
'exactness',
],
},
primaryKey: 'id',
transformer: (product: Record<string, unknown>) => ({
id: product.id,
title: product.title,
handle: product.handle,
description: product.description,
thumbnail: product.thumbnail,
collection_title: (product.collection as Record<string, unknown>)?.title,
collection_id: product.collection_id,
type_value: (product.type as Record<string, unknown>)?.value,
tags: (product.tags as Array<Record<string, unknown>>)?.map(
(t) => t.value
),
variant_prices: (product.variants as Array<Record<string, unknown>>)?.flatMap(
(v) => (v.prices as Array<Record<string, unknown>>) || []
),
}),
},
},
},
},
],
}6. Connecting the Stack: API Routes and Data Flow
The three components talk to each other through a straightforward API pattern. Next.js is the orchestration layer: it calls Medusa for commerce data (products, cart, checkout) and Meilisearch for search queries. The browser talks only to Next.js. This keeps your Medusa and Meilisearch instances behind a firewall, which is both a security and performance advantage.
For search, the Next.js frontend sends the user's query to a Next.js API route, which proxies it to Meilisearch and returns formatted results. This proxy pattern lets you add rate limiting, analytics tracking, and query logging without exposing your Meilisearch instance to the public internet.
For cart and checkout, Next.js calls Medusa's storefront API directly from Server Components (for initial page load) and from client-side fetch calls (for add-to-cart, quantity changes, and checkout steps). Medusa handles all payment processing through its Stripe plugin. The checkout flow never leaves your domain.
The on-demand revalidation webhook is what keeps everything in sync for SEO. When an admin updates a product price in Medusa's dashboard, Medusa fires a webhook to a Next.js API route that calls revalidatePath('/products/[slug]'). Within 1-2 seconds, the CDN-cached static page is regenerated with the new price. Your structured data is always accurate, which is critical for Product schema compliance and Google Merchant Center eligibility.
7. Cost Comparison: This Stack vs SaaS Alternatives
The cost advantage of this stack is not marginal. It is a 10-20x reduction in monthly infrastructure costs compared to the equivalent SaaS tooling. Here is the breakdown at three revenue tiers, with real numbers from stores I have built or migrated.
Monthly Cost Comparison: Self-Hosted vs SaaS at $50K/Month Revenue
| Component | Next.js + Medusa + Meilisearch | Shopify + Algolia | BigCommerce + Algolia |
|---|---|---|---|
| Commerce Platform | $0 (open source) | $79/mo (Basic) or $2,300/mo (Plus) | $299/mo (Pro) |
| Hosting / Infrastructure | $20-40/mo (VPS) | Included | Included |
| Frontend Hosting | $0-20/mo (Vercel) | Included | Included |
| Search | $0 (self-hosted Meilisearch) | $249-499/mo (Algolia) | $249-499/mo (Algolia) |
| Transaction Fees | 2.9% Stripe ($1,450) | 2.9% + platform fee ($1,450+) | 2.9% ($1,450) |
| Apps / Plugins (reviews, email, SEO) | $0-30/mo (open source or free tiers) | $200-500/mo | $100-300/mo |
| Total (excl. transaction fees) | $20-90/mo | $528-3,299/mo | $648-1,098/mo |
| Annual Savings | Baseline | $5,256-38,508 more | $6,696-12,096 more |
Based on a store with 2,000 SKUs, 50K monthly sessions, $50K monthly revenue. SaaS app costs vary by specific tools selected.
The honest caveat: you trade monthly SaaS cost for upfront development time. A production-ready store on this stack takes 40-80 hours of developer time to build. At a $100/hour developer rate, that is $4,000-8,000 in initial build cost. But the math still works overwhelmingly in favor of self-hosting: at $500/month in SaaS savings (the conservative end), the build cost is recovered in 8-16 months. At $2,000/month savings (the Shopify Plus tier), you break even in 2-4 months.
8. Performance Benchmarks
Performance is where this stack creates the widest gap over SaaS platforms. When you eliminate platform JavaScript overhead, theme rendering engines, and third-party app injection, the baseline performance of your store drops to near-theoretical minimums.
Core Web Vitals Benchmarks: Self-Hosted vs Platform Stores
| Metric | Next.js + Medusa (ISR) | Shopify (Dawn theme) | WooCommerce (optimized) | BigCommerce |
|---|---|---|---|---|
| LCP (mobile) | 1.1-1.4s | 2.8-4.2s | 2.2-3.8s | 2.5-3.5s |
| INP (mobile) | 80-120ms | 180-350ms | 200-500ms | 160-280ms |
| CLS | 0-0.02 | 0.05-0.15 | 0.08-0.25 | 0.04-0.12 |
| TTFB | 50-120ms | 400-900ms | 300-1,200ms | 350-800ms |
| Total JS Bundle | 85-140KB | 300-600KB | 400-900KB | 250-500KB |
| Lighthouse Performance | 95-100 | 45-72 | 35-65 | 50-75 |
Measured on product pages with 5+ images, review widget, and related products section. Mobile scores on Moto G Power (4G throttled). Shopify scores include 3-4 typical apps installed.
The TTFB difference is the most dramatic and the hardest for SaaS platforms to close. ISR pages on Vercel or Cloudflare Pages serve from the edge with no server computation. The HTML is pre-built and cached globally. Shopify's Liquid rendering engine processes every request through their server, which adds 400-900ms before the browser receives a single byte.
The JavaScript bundle difference is equally significant for INP. A Next.js storefront with Server Components sends only the JavaScript needed for interactivity (cart button, variant selector, image gallery). Everything else renders on the server and ships as HTML. Shopify themes load their entire JavaScript bundle on every page, including code for features the current page does not use. That dead JavaScript still parses and executes, blocking the main thread and degrading INP.
9. SEO Advantages of Owning Your Stack
SEO control is the least-discussed but highest-value advantage of running your own stack. On Shopify, you cannot change product URL prefixes from /products/. You cannot generate a custom sitemap that includes image elements with product photography alt text. You cannot implement hreflang tags without an app. You cannot add custom schema types beyond what your theme supports. Every one of these limitations has a measurable SEO cost.
With Next.js, you own your URL structure completely. Your product pages live at /blue-running-shoes if you want flat URLs, or /running-shoes/nike-air-max-270 if you want category-nested paths. Your sitemap is a TypeScript file that pulls product URLs from Medusa and generates exactly the XML Google needs, with lastmod timestamps from your actual product update dates, not arbitrary refresh intervals.
Structured Data Without Platform Limitations
On this stack, your structured data is pure TypeScript that generates JSON-LD from your Medusa product data. You implement Product schema with every field Google supports: variants, offers, aggregate rating, individual reviews, brand, category, ingredient lists, pros/cons. On Shopify, your schema is limited to what the theme or a $15/month app provides. I have audited 40+ Shopify stores and found broken schema on 34 of them because the theme's schema template could not handle their specific product data structure.
Internal Linking Architecture You Control
Internal linking is where platform stores lose the most organic visibility. On Shopify, you cannot programmatically add contextual internal links between related products, from collection pages to buying guides, or from blog posts to specific product variants. On this stack, you build your internal linking logic in Next.js components: related product sections pull from Medusa collections, breadcrumbs generate from your category hierarchy, and blog content links to product pages with exact anchor text you control.
The search data from Meilisearch adds another SEO advantage most stores miss entirely. Your search analytics reveal exactly what customers are searching for on your store. When 200 people search for "vitamin C serum for dark spots" and find nothing, that is a content gap you can fill with a new product or a buying guide that targets that exact query. Algolia provides this data too, but only on their $249+ plans. With Meilisearch, you own the search logs and can pipe them into any analytics tool you want.
10. When NOT to Use This Stack
This stack is not for everyone, and I want to be direct about who should not use it. If you are launching your first ecommerce store, have zero developer experience, and your product catalog is under 100 SKUs, start with Shopify. The $79/month is worth it for the speed of getting to market. You can always migrate later when the SaaS costs start eating into your margins.
If your team has no JavaScript or TypeScript experience and no budget to hire a developer, this stack will frustrate you. Medusa.js has a learning curve. Next.js requires understanding of React, server components, and deployment. Meilisearch is the simplest of the three, but you still need to manage a server. The admin dashboard handles day-to-day operations, but anything beyond adding products and managing orders requires code.
If you need same-day launch, a SaaS platform is the right call. A Shopify store can be live in 48 hours. This stack takes 2-4 weeks for a production-ready store. The investment is worth it for the long game, but if you have inventory arriving next week and need to start selling, do not try to build a custom stack under time pressure.
The sweet spot for this stack is stores doing $20K-500K/month with at least one person on the team who can write JavaScript, or the budget to hire a developer for 5-10 hours/month of maintenance. At that revenue level, the SaaS savings fund the developer cost with money left over, and the performance and SEO advantages compound into measurable organic growth.
FAQ
Next.js + Medusa + Meilisearch Stack FAQs
Where to Start: The 3-Step Migration Path
If you are running a SaaS ecommerce store and the numbers in this article made you uncomfortable, here is the progression I recommend. First, spin up a Medusa.js instance on a $10/month VPS and mirror your existing product catalog into it. This takes 2-4 hours and gives you a feel for the admin interface and API. Second, build a single product page in Next.js that fetches from Medusa and compare the Lighthouse score to your current store. The performance gap will be self-evident. Third, add Meilisearch and test search relevance against your current search tool.
You do not need to migrate everything at once. The composable nature of this stack means you can adopt it incrementally. Run Meilisearch alongside your existing Shopify search for a month to validate relevance. Build a headless Next.js frontend that points to your existing commerce backend before switching to Medusa.
The stores that outperform on organic search in 2026 are the ones that control their own technical infrastructure. Every month you pay a SaaS platform for suboptimal page speed, restricted URL structures, and limited schema markup is a month your competitors on owned stacks are compounding their SEO advantages. The migration cost is a one-time investment. The SaaS tax is forever.
Get Your Free Ecommerce SEO Audit
I audit ecommerce stores and deliver a prioritized list of SEO fixes with revenue impact calculated for each item. Whether you are on Shopify, WooCommerce, or already running a headless stack, I will show you exactly where you are leaving organic revenue on the table and what to fix first.
Aditya went above and beyond to understand our business needs and delivered SEO strategies that actually moved the needle.
Related Articles
Why Medusa.js is the strongest open-source alternative to Shopify for teams that want complete ownership of their commerce backend, data, and SEO architecture.
How to replace $500+/month Algolia bills with self-hosted Meilisearch. Covers typo tolerance, faceted filtering, and search-to-SEO content gap discovery.
The complete SEO playbook for headless commerce stores. Covers rendering strategy, structured data, sitemap generation, and the mistakes that tank organic traffic.
A practical guide to MACH architecture for ecommerce. Covers when composable commerce pays for itself and when a simpler setup is the better call.