Back

Deploying Next.js + Medusa v2 to Production: A Complete Guide

MM
MVPHub
12 min read

Deploying Next.js + Medusa v2 to Production: A Complete Guide

Medusa v2 starters ship with a complete local development stack — backend, admin dashboard, storefront, and seed data — but production deployment is something every team has to design themselves. This guide walks through the decisions and concrete steps for getting a Next.js + Medusa v2 stack running in production in a single afternoon.

This isn't a "happy path tutorial." It covers what breaks, what costs money, and how to run the backend without an incident every week.


TL;DR

  • You need three services: Medusa backend (Node.js), PostgreSQL, Redis. Plus storage, CDN, email, and monitoring.
  • Easiest path: Railway or Render for the backend, their managed Postgres + Redis, S3 or R2 for storage, Resend for email. ~$80-150/month.
  • More control: Fly.io or a VPS + managed database. ~$50-120/month.
  • Enterprise path: AWS or GCP with RDS + ElastiCache + ALB. ~$200-500+/month.
  • The hardest parts are database migrations, secret management, and Stripe webhook testing.
  • Budget for ops: 4-8 hours/month of routine maintenance.

Browse Medusa starter templates as your starting point.


Architecture at a glance

+----------------+       +------------------+       +----------------+
|  Next.js       |       |  Medusa Backend  |       |  Shopify / ... |
|  Storefront    |------>|  (Node.js API)   |<----->|  Stripe        |
|  (Vercel)      |       |  (Railway/Render)|       |  (payments)    |
+----------------+       +------------------+       +----------------+
        |                        |
        |                        +----> PostgreSQL (managed)
        |                        |
        |                        +----> Redis (managed)
        |                        |
        |                        +----> S3 / R2 (images)
        v                        |
+----------------+               +----> Resend / Postmark (email)
|  Customers     |
|  (browser /    |
|   mobile app)  |
+----------------+

Four deployable units:

  1. Next.js storefront — static-friendly, deploys to any Node or edge platform
  2. Medusa backend API — long-running Node.js process, needs persistent storage
  3. Medusa admin dashboard — usually runs alongside the backend on the same domain
  4. Worker (if you use background jobs) — same codebase as the backend, different process

Plus the managed dependencies (Postgres, Redis, storage, email, monitoring).


Step 1: Pick your hosting platform

There's no objectively "best" hosting for a Medusa stack. Pick based on how much ops work you want to do.

Option A — Railway or Render (easiest)

Good for: Solo founders and small teams. You want one dashboard, one bill, no AWS complexity.

What you get:

  • Managed deployments from a Git repo
  • Managed PostgreSQL and Redis add-ons
  • Automatic HTTPS and custom domains
  • Built-in logs and metrics
  • One-click rollbacks

Cost: $60-120/month for a realistic setup (backend service + Postgres + Redis + bandwidth).

Trade-offs: Less flexibility than raw cloud. You can't easily change database versions, tune Postgres config, or run custom network rules. You're trusting the platform.

Setup:

# Create a new Railway project
railway init

# Link your Medusa backend repo
railway link

# Add PostgreSQL and Redis services
railway add postgresql redis

# Set environment variables from .env.production
railway variables set DATABASE_URL=... REDIS_URL=... STORE_CORS=https://mystore.com

Railway auto-detects Medusa's package.json scripts and runs medusa start in production.

Option B — Fly.io or a VPS (middle ground)

Good for: Teams with DevOps capacity who want more control without jumping to AWS.

What you get:

  • Global deployment (Fly.io) or single-region VPS (DigitalOcean, Hetzner)
  • Custom Dockerfile deploys
  • SSH access to the runtime
  • More control over database tuning

Cost: $40-100/month for a VPS + managed Postgres. Fly.io runs similar for a small-to-medium stack.

Trade-offs: You're responsible for more of the operational surface. Backups, monitoring alerts, and security patches need your attention.

Option C — AWS / GCP (enterprise)

Good for: Teams already on AWS, or stores that need regional compliance, private networking, or high-volume scaling.

What you get:

  • RDS PostgreSQL (with point-in-time recovery)
  • ElastiCache Redis
  • ECS or App Runner for the Node.js service
  • Full VPC control and IAM scoping
  • Route 53 + CloudFront for DNS and CDN

Cost: $200-500+/month for a realistic production setup, before data transfer.

Trade-offs: Most operational overhead, most complexity. Also the most mature if you're already running AWS workloads.


Step 2: Set up the database

PostgreSQL is the single biggest operational decision. Don't self-host it unless you really know what you're doing.

Managed PostgreSQL options

ProviderGood forCost (entry)Notes
Railway PostgresSimplicity$5-20/monthSame dashboard as app
Render PostgresSimplicity$7-25/monthSame dashboard as app
NeonServerless Postgres$0-19/month free tierBranching useful for staging
SupabaseIf you also use their auth$25+/monthFull postgres + extras
AWS RDSEnterprise$30-300+/monthMaximum control
Fly.io PostgresGlobal$10-50/monthRegion-aware

For a production Medusa store, you need at least:

  • PostgreSQL 15 or 16 (Medusa v2 supports both)
  • 10 GB storage minimum, growing with your order volume
  • Daily automated backups with point-in-time recovery
  • Connection pooling — Medusa opens lots of connections; use PgBouncer if your provider doesn't pool automatically

Running migrations in production

This is where most deployments get scary. Medusa ships its own migration runner:

# In production, after every deploy
pnpm medusa db:migrate

Rule: migrations run before the new code starts, not after. A Dockerfile CMD wrapper works:

CMD ["sh", "-c", "pnpm medusa db:migrate && pnpm medusa start"]

Railway and Render have "pre-deploy" hooks that do this cleanly. On Fly.io, use a release command:

# fly.toml
[deploy]
  release_command = "pnpm medusa db:migrate"

Never let two backend instances try to run migrations at the same time. Migrations need to be serialized. The release-command pattern guarantees this.


Step 3: Set up Redis

Medusa v2 uses Redis for the event bus, cache, and (optionally) workflow state.

Managed Redis options

ProviderGood forCost
Railway RedisMatches your Postgres$5-15/month
Render RedisMatches your Postgres$10-25/month
Upstash RedisServerless, pay-per-request$0-20/month typical
AWS ElastiCacheEnterprise$15-100+/month

For a Medusa stack, 256 MB is enough for stores under ~10k orders/month. Scale up when you start seeing memory pressure in Medusa logs.

Important: Medusa assumes Redis is always up. A transient Redis outage can cause request failures. Pick a provider with good uptime SLAs, and don't run Redis on the same tiny VPS as your database.


Step 4: Set up file storage

Product images need to live somewhere. Medusa supports S3-compatible storage out of the box.

Options, from cheapest to fanciest

  • Cloudflare R2 — S3-compatible, zero egress fees, $0.015/GB/month. Best default for most stores.
  • AWS S3 — The original. Egress fees can bite you.
  • Backblaze B2 — Cheap, S3-compatible, decent free tier.
  • Vercel Blob — Integrated with Vercel deployments, premium pricing.

Configure in your Medusa medusa-config.ts:

export default defineConfig({
  projectConfig: {
    // ...
  },
  modules: [
    {
      resolve: "@medusajs/medusa/file",
      options: {
        providers: [
          {
            resolve: "@medusajs/medusa/file-s3",
            id: "s3",
            options: {
              file_url: process.env.S3_FILE_URL,
              access_key_id: process.env.S3_ACCESS_KEY_ID,
              secret_access_key: process.env.S3_SECRET_ACCESS_KEY,
              region: process.env.S3_REGION,
              bucket: process.env.S3_BUCKET,
              endpoint: process.env.S3_ENDPOINT, // R2 or custom
            },
          },
        ],
      },
    },
  ],
})

Images are served directly from the CDN in front of your storage bucket. The storefront gets the image URLs from the Medusa API and renders them with next/image or a direct <img> tag.


Step 5: Configure Stripe

Stripe is the default payment provider for MVPHub Medusa templates. The setup is:

  1. Create a Stripe account (or use an existing one)
  2. Enable the payment methods you want (card, Apple Pay, Google Pay, etc.)
  3. Copy your secret key from the Stripe dashboard
  4. Set env vars in your Medusa backend:
    STRIPE_API_KEY=sk_live_...
    STRIPE_WEBHOOK_SECRET=whsec_...
    
  5. Register a webhook endpoint in Stripe pointing at your Medusa backend:
    https://api.yourstore.com/hooks/payment/stripe
    
  6. Copy the webhook signing secret from Stripe back into STRIPE_WEBHOOK_SECRET

Test it by using Stripe's test mode first. Create a test payment, watch it show up in Medusa's admin dashboard, and verify the order transitions through the expected states.

See the Stripe checkout integration guide for the full checkout flow and webhook patterns.


Step 6: Deploy the Next.js storefront

The storefront is the easy part. Deploy it to Vercel, Cloudflare Pages, or any Next.js-compatible host.

Environment variables

The storefront needs three critical env vars:

NEXT_PUBLIC_MEDUSA_BACKEND_URL=https://api.yourstore.com
NEXT_PUBLIC_MEDUSA_PUBLISHABLE_KEY=pk_...
NEXT_PUBLIC_DEFAULT_REGION=us

The publishable key is created in the Medusa admin under "Settings → Publishable API Keys." It scopes the storefront's access to the backend.

Vercel deployment

vercel --prod

Vercel detects Next.js, runs pnpm build, and deploys. Set the env vars in the Vercel project settings. Set the root directory to apps/storefront if you're in a monorepo.

Connecting to the backend

In production, the storefront hits your backend over HTTPS. CORS has to be configured correctly:

// apps/backend/medusa-config.ts
export default defineConfig({
  projectConfig: {
    http: {
      storeCors: process.env.STORE_CORS ?? "https://mystore.com",
      adminCors: process.env.ADMIN_CORS ?? "https://admin.mystore.com",
      authCors: process.env.AUTH_CORS ?? "https://mystore.com",
    },
  },
})

If you get "CORS error" in browser console, 99% of the time STORE_CORS is wrong.


Step 7: Set up email

Order confirmations, password resets, and other transactional emails need to go out reliably.

Options:

  • Resend — $0-20/month typical, clean DX
  • Postmark — $15-50/month, enterprise-grade deliverability
  • AWS SES — Cheapest at scale, lots of setup
  • SendGrid — Middle ground

Configure Medusa's notification provider in medusa-config.ts:

{
  resolve: "@medusajs/medusa/notification",
  options: {
    providers: [
      {
        resolve: "./src/modules/resend", // Custom Resend provider
        id: "resend",
        options: {
          api_key: process.env.RESEND_API_KEY,
          from_email: "orders@yourstore.com",
        },
      },
    ],
  },
}

Test deliverability early. An order confirmation that lands in spam is almost worse than one that doesn't send at all.


Step 8: Monitoring and alerting

Minimum viable monitoring:

  • Uptime checks — Pingdom, Better Uptime, or UptimeRobot pinging your /health endpoint every minute
  • Error tracking — Sentry on both backend and storefront
  • Log aggregation — Whatever your host gives you (Railway logs, Vercel logs) is usually enough to start
  • Database metrics — Your managed Postgres provider's built-in dashboard

Alerts to set up on day one:

  • Backend down for > 2 minutes
  • Error rate > 1% of requests
  • Database CPU > 80% sustained
  • Disk usage > 80%
  • Stripe webhook delivery failures

Don't over-engineer monitoring at launch. The goal is to know when something is broken, not to build a Datadog dashboard nobody reads.


Step 9: Operational runbook

Before you flip DNS to production, write down the answers to these:

  1. How do I restart the backend? (Platform-specific command)
  2. How do I roll back a bad deploy? (Git revert + redeploy, or platform-native rollback)
  3. How do I restore the database from a backup? (Documented, tested, ideally practiced once)
  4. How do I invalidate the CDN cache? (If using CloudFront/Cloudflare)
  5. How do I trigger a manual Stripe webhook retry? (Stripe dashboard → Developers → Webhooks)
  6. Who gets paged for a P1? (Even if it's just you at 3am, write it down)
  7. What's the communication plan if Stripe goes down? (Status page, customer email, refund policy)

This runbook lives in the repo as docs/runbook.md or similar. Update it every time you hit a new failure mode.


Common deployment mistakes

Forgetting to run migrations before starting the new version. The service comes up, hits a schema it doesn't understand, and crashes. Use a release command.

Using the same Postgres database for staging and production. Obvious when said out loud, embarrassingly common. Use a separate database (or a separate Postgres service) for staging.

Exposing the admin dashboard to the public internet without IP allowlisting. The admin login is strong, but you don't need to hand attackers a target. Put it behind a VPN, IP allowlist, or at least a WAF rule.

Running the storefront and backend on the same domain without proper CORS. You'll get cookies and CORS errors you don't understand. Use subdomains: store.com for the storefront, api.store.com for the backend, admin.store.com for the admin dashboard.

Skipping the smoke test. After every deploy, load the homepage, add a product to cart, and complete a test checkout. Automate this if you can; do it manually if you can't.


Ongoing maintenance

A production Medusa stack needs about 4-8 hours of routine work per month. In order of frequency:

  • Weekly: review error tracker, check uptime, verify backups actually ran
  • Monthly: apply security patches (Node.js, npm packages, OS), review database size and query performance
  • Quarterly: upgrade Medusa minor versions, audit env var drift between environments, test database restore
  • Yearly: major version upgrades (Node.js LTS, Postgres, Medusa v3 when it lands)

This is less work than most teams expect but more than zero. Budget for it explicitly instead of assuming "it'll be fine."


Next steps

Production deployment isn't glamorous, but it's the gap between "demo that works on my laptop" and "store that customers can actually buy from." Walk through this checklist once, and the second store you launch will take a third the time.

Working products with full source code — live demo, one-time purchase, instant delivery.

Browse the marketplace
GR LIVEecommerce

Groover Multi-Purpose Store

$149

A fully-polished, multi-purpose e-commerce template engineered for brands that need the full feature set on day one — not a minimal starter you outgrow in a month. Groover ships with a live Medusa-backed catalog, category + collection merchandising, search with multi-facet filtering (category, collection, price, sale, stock, sort), product-detail with variant selection + image gallery + stock messaging + related products, Stripe Elements checkout with provider-aware setup panels, account dashboard with guest order lookup and authenticated order history, customer auth with login/register/logout/profile edit, wishlist with guest browser persistence and signed-in customer sync, blog list + detail, store directory, track-order page, branded 404, About/Contact/FAQ/Terms legal shell, GTM-friendly dataLayer wired into PDP/cards/wishlist/cart/checkout/search, locale + RTL foundation with persistent language switcher, PWA installability baseline, theme switching that applies before hydration and persists in both local storage and cookies, header active-route navigation with live mini-cart summary, skip-link / focus accessibility basics, app-level and route-level loading fallbacks, a recoverable error boundary, generated robots.txt and sitemap.xml, shared SEO metadata helpers, and a Playwright / Vitest / Lighthouse test harness. Every copy string lives in a typed content map so rebranding is a find-and-replace pass, not a code rewrite. Deploy it as-is or use it as the most complete starting point you can buy for a serious storefront.

★★★★★0 soldAstro · Medusa
FU LIVEecommerce

Furniture Store

$49

An elegant furniture and home furnishing e-commerce app with a design-forward Next.js storefront for SEO-optimized product pages and server-rendered category browsing. Alternative framework and mobile ports are available on demand. The visual design emphasizes large product imagery, room-based browsing, and material/color variant selection. Built with Radix UI, shadcn/ui, Tailwind CSS, and Framer Motion for a premium feel. Connects to any headless commerce backend — Medusa JS SDK integration is included. Form handling via React Hook Form with Zod validation ensures robust checkout and account flows. Great for furniture brands, interior design shops, or home decor marketplaces.

★★★★★0 soldExpo · Next.js
PE LIVEecommerce

Perfume Store

$49

A luxury-styled perfume and fragrance e-commerce app built for premium brand presentation. The ready-to-buy Next.js storefront features rich product pages with scent profiles, bottle size variants, gift set options, and server-rendered collections. Mobile, backend, and alternative framework ports are available on demand. The design uses shadcn/ui and Tailwind CSS with an elegant, minimalist aesthetic suited for luxury goods. Easy to customize — swap product data, update branding, and deploy. Perfect for perfume brands, fragrance boutiques, or niche scent marketplaces.

★★★★★0 soldMedusa · Expo

Keep reading — popular eCommerce guides on MVPHub.

All eCommerce articles

Explore other MVP verticals

MVPHub publishes templates and guides for ecommerce, SaaS, marketplaces, AI apps, booking platforms, subscription stores, directory sites, and more. Here are fresh picks from other verticals.