Medusa.js Frontend Integration (Next.js/Gatsby)
Medusa provides Store API and optional TypeScript SDK @medusajs/js-sdk for frontend. Official Next.js Storefront Starter — ready start point, but production projects need significant customization. Gatsby used less — suits catalogs with slow-changing content via SSG.
Setup Medusa JS SDK
npm install @medusajs/js-sdk @medusajs/types
import Medusa from '@medusajs/js-sdk';
export const medusaClient = new Medusa({
baseUrl: process.env.NEXT_PUBLIC_MEDUSA_BACKEND_URL!,
auth: {
type: 'session',
},
publishableKey: process.env.NEXT_PUBLIC_MEDUSA_PUBLISHABLE_KEY,
});
Next.js App Router: Product Page
export async function generateStaticParams() {
const { products } = await medusaClient.store.product.list({
fields: 'handle',
limit: 200,
});
return products.map(p => ({ handle: p.handle }));
}
export async function generateMetadata({ params }): Promise<Metadata> {
const { products } = await medusaClient.store.product.list({
handle: params.handle,
});
const product = products[0];
return {
title: product.title,
description: product.description,
};
}
export default async function ProductPage({ params }) {
const { products } = await medusaClient.store.product.list({
handle: params.handle,
fields: '*variants,*variants.prices,*images',
});
const product = products[0];
if (!product) notFound();
return <ProductTemplate product={product} />;
}
Cart Management
'use client';
import { createContext, useContext, useState, useEffect } from 'react';
type CartContextType = {
cart: HttpTypes.StoreCart | null;
addItem: (variantId: string, quantity: number) => Promise<void>;
};
const CartContext = createContext<CartContextType | null>(null);
export function CartProvider({ children }) {
const [cart, setCart] = useState(null);
useEffect(() => {
const cartId = localStorage.getItem('cart_id');
if (cartId) {
medusaClient.store.cart.retrieve(cartId)
.then(({ cart }) => setCart(cart));
}
}, []);
const addItem = async (variantId: string, quantity: number) => {
let currentCart = cart;
if (!currentCart) {
const { cart: newCart } = await medusaClient.store.cart.create({
region_id: process.env.NEXT_PUBLIC_MEDUSA_REGION_ID,
});
localStorage.setItem('cart_id', newCart.id);
currentCart = newCart;
}
const { cart: updatedCart } = await medusaClient.store.cart.createLineItem(
currentCart.id,
{ variant_id: variantId, quantity }
);
setCart(updatedCart);
};
return (
<CartContext.Provider value={{ cart, addItem }}>
{children}
</CartContext.Provider>
);
}
export const useCart = () => useContext(CartContext);
Multi-Region
import { NextRequest, NextResponse } from 'next/server';
const REGION_MAP = {
RU: process.env.MEDUSA_REGION_RU!,
BY: process.env.MEDUSA_REGION_BY!,
DEFAULT: process.env.MEDUSA_REGION_DEFAULT!,
};
export function middleware(request: NextRequest) {
const country = request.geo?.country ?? 'DEFAULT';
const regionId = REGION_MAP[country] ?? REGION_MAP.DEFAULT;
const response = NextResponse.next();
response.cookies.set('medusa_region', regionId, { maxAge: 60 * 60 * 24 });
return response;
}
Timeline
- Next.js Storefront Starter + customization: 2–3 weeks
- Custom Next.js from scratch (App Router, SSR/SSG, cart, checkout): 6–10 weeks
- Gatsby SSG catalog with dynamic cart (hybrid): 4–6 weeks
- Multi-region with localization: +2–3 weeks







