Prerequisites Before You Start
You need Node.js 20 or above and a PostgreSQL database available either locally or via a hosted provider like Supabase or Neon. The Medusa CLI and Next.js both depend on Node.js, and Medusa requires PostgreSQL specifically since it does not support SQLite for production setups. If you are working locally, install PostgreSQL and create a database before running any install commands.
You will also need a Medusa publishable API key. This is generated inside the Medusa admin dashboard after your backend is running, and it authenticates all storefront requests to the Medusa API. The setup command described below handles this automatically if you use the monorepo approach, but it is worth understanding what it does so you can manage it when deploying to production.
Step 1: Install Medusa Using create-medusa-app
The recommended install method in 2026 is the create-medusa-app command, which scaffolds a monorepo containing both the Medusa backend and the Next.js storefront in a single project structure. Run the following in your terminal:
npx create-medusa-app@latest my-store
The CLI will prompt you for your project name and database connection string. Once complete, you will have a directory structure with the Medusa backend under apps/backend and the Next.js storefront under apps/storefront. Both are configured to communicate with each other out of the box, with the publishable API key already set in the storefront's environment variables.
Step 2: Understanding the App Router Folder Structure
The storefront follows the Next.js App Router convention where every folder inside the app directory maps to a route. The key directories you will work with most are:
The app/[countryCode]/(main) directory handles all the storefront pages scoped to a region. This is where product listing pages, product detail pages, cart, and checkout live. The country code segment enables multi-region routing automatically based on the user's detected region, which Medusa handles via its regions API.
The lib/data directory contains all the functions that call the Medusa JS SDK to fetch products, manage carts, handle authentication, and retrieve shipping options. These are async server-side functions that run inside React Server Components, which means data fetching happens on the server and no API secrets are exposed to the browser.
The modules directory contains all the UI components organised by commerce feature: products, cart, checkout, account, layout, and so on. Each module folder is self-contained with its own components, and this structure makes it straightforward to replace individual UI sections without touching the data layer.
Build your headless storefront
Let's TalkStep 3: Connecting the Storefront to Medusa with the JS SDK
The Medusa JS SDK replaces the older medusa-js client library and is the standard way to interact with the Medusa API from a Next.js storefront in 2026. The SDK is initialised once in a shared config file and imported across your data functions. A typical initialisation looks like this:
import Medusa from "@medusajs/js-sdk"
export const sdk = new Medusa({
baseUrl: process.env.NEXT_PUBLIC_MEDUSA_BACKEND_URL,
publishableKey: process.env.NEXT_PUBLIC_MEDUSA_PUBLISHABLE_KEY,
})With this SDK instance available, fetching products in a Server Component requires a single awaited call. The SDK handles authentication headers, JSON parsing, and error formatting so your data functions stay clean and readable without boilerplate fetch logic.
Step 4: Fetching Products and Building Product Pages
Product listing and product detail pages are the most customised part of any storefront. In the App Router, these are Server Components by default, which means the product data is fetched on the server before any HTML is sent to the browser. This gives headless Medusa storefronts excellent Core Web Vitals scores for product pages out of the box.
Product list pages use the sdk.store.product.list() method with filter parameters for collection, category, or tag. Product detail pages use sdk.store.product.retrieve() with the product handle extracted from the dynamic route segment. The starter template implements both of these patterns, and the code is straightforward to extend with custom fields, variant selectors, or related product recommendations.
Step 5: Cart and Checkout with React Server Components
Cart state in the App Router storefront is managed through a combination of Server Components for display and Client Components for interactions. The cart ID is stored in a cookie, which the Medusa middleware reads on every request to ensure the cart is always available server-side without a client-side fetch.
The add-to-cart action, quantity updates, and cart removal are all implemented as Next.js Server Actions, which means they run on the server and update the cart via the Medusa API without requiring a client-side API call. This approach keeps sensitive Medusa credentials off the client and makes the cart interaction feel instantaneous with optimistic UI updates.
Checkout follows the same pattern described in our previous article on building a custom Medusa.js checkout flow: address collection, shipping selection, and payment initialisation are all driven by Server Actions that update the cart object until the final complete-cart call places the order.
Start your Medusa.js project
Let's TalkStep 6: Running and Testing the Storefront Locally
Start both the Medusa backend and the Next.js storefront from the monorepo root using the workspace dev command. The Medusa backend runs on port 9000 by default and the storefront on port 8000. Navigate to localhost:8000 to see the storefront connected to your local Medusa instance, with the admin dashboard available at localhost:9000/app.
Seed the database with test products using the Medusa seed command before testing the storefront. The starter includes a seed script that populates a default region, a sample product catalogue, and shipping options so you have something to browse and purchase end-to-end immediately.
Common Setup Issues and How to Resolve Them
The most frequent issue is a CORS error when the storefront tries to reach the backend. Medusa's CORS configuration must include the storefront URL in the storeCors setting inside medusa-config.ts. In development this is http://localhost:8000 and in production it is your deployed storefront domain. Missing this is the single most common cause of a blank storefront on first run.
The second common issue is a missing or expired publishable API key. If you reset your Medusa database, the key is regenerated and the storefront environment variable needs updating to match. The storefront will silently fail to load products if the key does not match what the backend has on record.
The official Medusa Next.js Starter documentation covers the full environment variable reference, monorepo workspace setup, and deployment guides for Vercel, Railway, and self-hosted environments. It is the authoritative reference for anything not covered in this setup guide.
The App Router architecture is not just a technical preference. For commerce storefronts, Server Components eliminate the client-side waterfall that used to make headless storefronts feel slower than platform-hosted alternatives. A correctly set up Medusa plus Next.js App Router storefront renders product pages faster than most Shopify themes on equivalent infrastructure, which is a meaningful advantage when page speed directly affects conversion.
Written by
Manikandan Arumugam
CDO
