Skip to content
Docs

How to deploy Medusa on Vercel

Deploy a Medusa storefront on Vercel, including where the backend runs, which environment variables the current starter reads, and how to cache the catalog.

Vercel

A Medusa application deploys in two parts. The Next.js storefront runs on Vercel like any other Next.js project. The Medusa server and worker are long-lived Node.js processes that run on a persistent host, alongside PostgreSQL and Redis.

Here's how to deploy the storefront, starting with that split and ending with the errors that appear after the first deploy.

Copy link to headingShould you use Medusa or a hosted commerce platform?

Medusa is an open-source headless commerce engine. It ships commerce logic as modules covering products, carts, orders, pricing, and fulfillment, and exposes them through a Store API and an Admin API that any frontend can call. You run the engine yourself, backed by PostgreSQL and Redis.

The choice between Medusa and a hosted platform such as Shopify comes down to how much of the commerce workflow you need to own:

FactorMedusa fits whenA hosted platform fits when
Checkout workflowYou need to define the workflow in code, including custom steps and B2B quote flowsStandard checkout with extension points covers your flow
Data and schemaYou need direct access to the commerce schema and your own backup strategyVendor-managed data is acceptable
ExtensibilityYou want to write modules and workflows in Node.jsThe vendor's app ecosystem covers your requirements
OperationsYour team can run a server, worker, database, and RedisYou want the vendor to run the platform

Choosing Medusa means running a server, worker, PostgreSQL instance, and Redis instance, all of which need to be live before the storefront can serve a product page.

Copy link to headingWhat you need to deploy Medusa on Vercel

Have these in place before you start:

  • Node.js runtime: Version 20.19.0 or later, or 22.12.0 or later, and below version 25. The same range covers the backend and the storefront, per Medusa's documented prerequisites.
  • Medusa application: Version 2.14.0 or later, created with create-medusa-app, which installs the dtc-starter monorepo. The archived standalone starter is not a supported starting point.
  • Medusa server and worker: Both processes running on a persistent Node.js host, such as a container platform, a virtual machine, or Medusa Cloud. Medusa's deployment guide puts the host at 2 GB of RAM or more. The storefront points at that host's public URL.
  • PostgreSQL and Redis: Both provisioned alongside the backend. Redis backs the Event, Workflow Engine, Locking, and Caching modules in production.
  • Publishable API Key: Created in Medusa Admin with at least one sales channel attached. The storefront sends it on every Store API request.
  • Vercel account: Pro or Enterprise for multiple function regions or durations above 300 seconds. Hobby covers a single-region deploy.

With the backend running and reachable at a public URL, the storefront deployment takes a few minutes.

Copy link to headingHow a Medusa deployment splits across Vercel and a Node.js host

Medusa's worker mode runs two long-lived processes in production. A server process answers API and Admin requests, and a worker process runs subscribers, scheduled jobs, and background workflows.

Three properties of those processes put them outside what Vercel Functions are built for:

  • Queues: Production Medusa runs Redis-backed Event, Workflow Engine, Locking, and Caching modules. A process has to stay running to consume them between requests.
  • Invocation: A Vercel Function starts when a request arrives. The worker has no inbound request to start it, since subscribers fire on events and scheduled jobs fire on a clock.
  • Duration: Background workflows and scheduled jobs are not bounded by a request. Functions on Fluid compute default to 300 seconds, with an 800-second maximum on Pro and Enterprise and a 1,800-second extended maximum in beta.

Run the server and worker on a host built for persistent Node.js services, and deploy the Next.js storefront on Vercel. The Medusa Admin is a Vite single-page application that the backend serves at /app by default, so it deploys with the server rather than separately.

Copy link to headingStep 1: Create a Medusa app with create-medusa-app

Start new projects from the command-line rather than from a template deploy button:

Terminal
npx create-medusa-app@latest my-store --with-nextjs-starter

This installs the dtc-starter monorepo, with the backend in apps/backend and the storefront in apps/storefront.

Avoid the older path. Vercel's Medusa template page is still live, and the repository behind it, medusajs/nextjs-starter-medusa, was archived on July 2, 2026.

Copy link to headingStep 2: Deploy the Medusa storefront to Vercel

Connect the storefront directory to a new Vercel project through the dashboard, or from the command-line with the Vercel CLI, Vercel's command-line interface:

Terminal
cd apps/storefront
vercel

Set the project's root directory to apps/storefront so the build runs against the storefront rather than the monorepo root. The first deploy builds successfully and fails at runtime until the storefront's environment variables are set.

Copy link to headingStep 3: Set the Medusa storefront environment variables

The current dtc-starter reads NEXT_PUBLIC_MEDUSA_BACKEND_URL in its middleware and JS SDK configuration. The archived standalone starter used MEDUSA_BACKEND_URL. In the current starter, setting only the unprefixed variable leaves the SDK using its http://localhost:9000 fallback. Set the prefixed variable to your deployed Medusa server’s public URL.

Set these four variables in your Vercel project with values appropriate to each environment:

  • NEXT_PUBLIC_MEDUSA_BACKEND_URL: Set this to your deployed Medusa server’s public URL, such as https://your-backend.example.com.
  • NEXT_PUBLIC_MEDUSA_PUBLISHABLE_KEY: Set this to the publishable key from Medusa Admin, such as pk_your_key_here. Associate the key with the sales channels your storefront uses.
  • NEXT_PUBLIC_DEFAULT_REGION: Set this to the two-letter country code to use when country detection finds no match. The starter defaults to dk (Denmark). Choose a country included in one of your configured Medusa regions.
  • NEXT_PUBLIC_BASE_URL: Set this to your storefront’s public URL, such as https://your-store.example.com.

The backend hostname is public in this architecture. What protects your Store API is the publishable key, its sales channel scope, and cross-origin resource sharing (CORS) rules on the backend. Any variable you add that is a genuine secret, such as a shared secret on a revalidation route, takes no NEXT_PUBLIC_ prefix. Next.js inlines prefixed values into the JavaScript it sends to the browser. Set all four for Production, Preview, and Development in project settings so preview deployments resolve a backend too.

Copy link to headingStep 4: Run Vercel Functions near your Medusa database

Vercel Functions default to Washington, D.C. (iad1). Storefront functions call the Medusa API, so choose a function region close to the Medusa server. Host the Medusa server close to PostgreSQL to reduce latency between the backend and its database. Static assets and eligible cached page responses are distributed through the CDN.

If the database runs elsewhere, set the function region in vercel.json:

vercel.json
{
"$schema": "<https://openapi.vercel.sh/vercel.json>",
"regions": ["fra1"]
}

Use fra1 for Frankfurt or sin1 for Singapore. Hobby projects run functions in a single region, Pro in up to five, and Enterprise in all of them.

Pinning functions to one region does not change where the rest of the request is served. Static files and cached pages still come from the CDN, and Routing Middleware deploys to all regions by default, so country detection stays close to the visitor. On Hobby, Routing Middleware runs in fewer regions.

Copy link to headingStep 5: Cache the Medusa catalog with ISR

Product and collection pages change rarely and carry most of the traffic, which makes them the right candidates for Incremental Static Regeneration (ISR). Since Next.js 15, fetch is not cached by default, so opt in and tag the request:

const product = await fetch(`${process.env.NEXT_PUBLIC_MEDUSA_BACKEND_URL}/store/products/${id}`, {
headers: { 'x-publishable-api-key': process.env.NEXT_PUBLIC_MEDUSA_PUBLISHABLE_KEY! },
cache: 'force-cache',
next: { tags: [`product-${id}`] },
})

A cache hit is served from the nearest of Vercel’s 19 compute-capable regions, reached through the 126 points of presence that terminate the connection and route it there. On a miss, concurrent requests for the same path collapse into one function invocation per region, and a revalidation updates every region within 300 milliseconds.

Keep cart and account state out of that cache. Any Server Component that reads cookies opts its whole route into request-time rendering. Wrap cart state, account UI, and cookie-based pricing in <Suspense> boundaries, and give pricing its own boundary.

To invalidate on catalog changes, follow Medusa's revalidation guide and add a subscriber on product.created, product.updated, and product.deleted that calls a route handler on the storefront. Inside the handler, call revalidateTag, which takes a second argument in Next.js 16. Use time-based revalidation for anything that displays stock, since inventory changes do not emit the same product events.

Copy link to headingStep 6: Wire CORS, sessions, and Stripe webhooks to Medusa

These settings live on the Medusa backend and decide whether the deployed storefront can talk to it. Configure the following on your backend host:

  • Store and Admin origins: Set storeCors to the storefront origin and adminCors to the Admin origin, both under projectConfig.http in medusa.config.ts. Leave off the trailing slash, since an origin that ends in one will not match.
  • Auth origins: Set authCors to the storefront and Admin origins combined, because Medusa applies it to every route starting with /auth. Leaving one out blocks login from that origin while the rest of the storefront keeps working.
  • Preview origins: Add the preview origins you intend to allow to storeCors and, where authentication requires it, authCors. Use exact origins or a narrowly scoped regular expression for your project’s preview domains. Avoid a blanket /vercel\.app$/ rule, which also permits unrelated sites hosted on Vercel.
  • Session cookies: The starter authenticates through the JS SDK, which keeps the session in a cookie. Any fetch you write outside the SDK needs credentials: 'include', or that cookie never reaches the Store API. For custom server-side requests, follow the starter’s getAuthHeaders() pattern.
  • Stripe webhooks: Point Stripe's webhook endpoint at the Medusa server, where the payment module verifies the signature. Pointing it at the Vercel deployment URL leaves orders unpaid, because the payment event never reaches the engine.

For preview deployments, build Open Graph image URLs from VERCEL_PROJECT_PRODUCTION_URL, which is set in previews as well. The value omits the protocol scheme, so prepend https://. Point the preview-scoped backend URL at a staging Medusa server, or turn on Deployment Protection so preview traffic cannot reach production commerce data.

Copy link to headingHow to troubleshoot a Medusa deployment on Vercel

Most first-week failures trace back to one of six causes. Work through them in order.

Copy link to heading1. The backend URL variable does not match the starter

The build succeeds and routes return 404s or fetch errors because the storefront resolved its backend to localhost. The current starter reads NEXT_PUBLIC_MEDUSA_BACKEND_URL, and the unprefixed MEDUSA_BACKEND_URL still appears in places that predate it, including sections of Medusa's own storefront documentation. Set the prefixed name and redeploy.

Copy link to heading2. A trailing newline in the backend URL

A newline pasted into the backend URL breaks the request URL built from that value and returns a 500 in production. Run vercel env pull and inspect the quoted value, since the stray character is invisible in the dashboard.

Copy link to heading3. The publishable key has no sales channel

Product requests fail while the key itself is valid, because a publishable key returns nothing until a sales channel is attached. Attach one under Settings in Medusa Admin.

Copy link to heading4. Product images return 400

next/image rejects images whose hostname is not listed in images.remotePatterns, and matching is exact down to protocol and pathname. Add the S3 or MinIO host in apps/storefront/next.config.js. The older images.domains field is deprecated since Next.js 14.

Copy link to heading5. The catalog stays stale after Admin changes

Product pages keep the old price because nothing invalidated the tag. Confirm the subscriber runs on the worker process under the Redis Event Module rather than the in-memory one, and that the revalidation route returns a success response.

Copy link to heading6. A cold backend times out the function

A 504 on the first request after a backend restart usually means a cold start stacked on a slow query. Raise maxDuration on the affected route segment, keeping in mind that values above 300 seconds require Pro or Enterprise, and treat a warm backend as the durable fix.

Working through these in order resolves most deployment issues without changes to the storefront code.

Copy link to headingNext steps

With the backend reachable and the storefront caching correctly, the deployment is ready to go live. Start a new Vercel project and connect the apps/storefront directory, or browse the templates for a commerce starting point.

Copy link to headingFrequently asked questions

Can I host the Medusa backend on Vercel?

No. The Medusa server and worker are long-lived processes that consume Redis-backed queues and run background jobs beyond a request's lifetime. They need a persistent Node.js host, such as a container platform, a virtual machine, or Medusa Cloud. Deploy the Next.js storefront on Vercel and point it at that backend's public URL.

Which backend URL variable does the current Medusa starter use?

Use NEXT_PUBLIC_MEDUSA_BACKEND_URL. The current dtc-starter reads the prefixed name because the JS SDK calls the Store API from the browser, so the hostname is meant to be client-visible. The unprefixed MEDUSA_BACKEND_URL belongs to the archived standalone starter, and mixing the two produces 404s across the site.

Can I host the Medusa Admin dashboard on Vercel?

The Admin is a Vite single-page application that the backend serves at /app by default, so it normally deploys with the server. You can build it separately and host the output on Vercel, which requires setting the Admin's backend URL and path in medusa.config.ts so the dashboard resolves the API correctly.

Which Redis should I use with Medusa on Vercel?

Provision Redis wherever the backend host can reach it. A Redis integration from the Vercel Marketplace works. It injects credentials into the Vercel project rather than the backend, so copy the connection string into the backend's own REDIS_URL. Medusa's Event, Workflow Engine, Locking, and Caching modules read it there.

How do I avoid CORS errors on Medusa preview deployments?

Set storeCors on the Medusa backend to a pattern such as /vercel\.app$/ rather than a list of exact origins. Each branch push generates a new preview URL, so an explicit list breaks on the next deployment. Apply the same pattern to authCors if previews need to exercise login flows.

Related documentation

More Next.js guides