Next.js 15 Storefront Patterns for Medusa v2
Before writing code
Fetch live docs:
- Web-search
site:docs.medusajs.com storefront nextjs for Medusa storefront guides
- Fetch
https://docs.medusajs.com/resources/references/js-sdk for JS SDK method reference
- Web-search
site:nextjs.org docs app router for latest Next.js 15 App Router conventions
- Web-search
site:docs.medusajs.com nextjs starter storefront for starter template patterns
- Web-search
site:docs.medusajs.com publishable api key storefront for API authentication setup
App Router Conventions
Directory Structure
src/app/
├── layout.tsx — Root layout (providers, SDK)
├── page.tsx — Home page
├── (main)/products/ — Product listing + [handle]/
├── (main)/cart/ — Cart, checkout, account
└── lib/medusa.ts — SDK client initialization
Key App Router Concepts
| Concept |
File Convention |
Purpose in Medusa Storefront |
| Layout |
layout.tsx |
Wrap pages with providers, header, footer |
| Page |
page.tsx |
Route entry point (server component default) |
| Loading |
loading.tsx |
Streaming fallback UI |
| Error |
error.tsx |
Error boundary per route segment |
| Not Found |
not-found.tsx |
404 page for invalid product handles, etc. |
| Route Groups |
(group)/ |
Organize without affecting URL |
| Dynamic Routes |
[param]/ |
Product handles, category slugs |
| Parallel Routes |
@slot/ |
Simultaneous layout regions |
Server vs Client Components
Decision Matrix
| Criterion |
Server Component |
Client Component |
| Data fetching |
Fetch from Medusa API directly |
Use Tanstack Query hooks |
| SEO |
Full HTML rendered on server |
Not indexed by crawlers |
| Interactivity |
No event handlers |
onClick, onChange, etc. |
| State |
No useState/useEffect |
Full React hooks |
| Examples |
Product listing, product detail, categories |
Cart actions, quantity selector, search |
| Directive |
None (default) |
"use client" at top |
Component Split Pattern
ProductPage (server)
├── ProductInfo (server) — Title, description, price
├── ProductImages (server) — Image gallery markup
├── AddToCart (client) — "use client", quantity, button
└── RelatedProducts (server) — Fetched server-side
Medusa JS SDK Integration
SDK Initialization and Configuration
| Option |
Purpose |
Example Value |
baseUrl |
Medusa server URL |
http://localhost:9000 |
publishableKey |
Store API authentication |
From admin dashboard |
auth.type |
Authentication strategy |
"session" or "jwt" |
Common SDK Methods
| Domain |
Method Pattern |
Component Type |
| Products |
sdk.store.product.list() |
Server (listing) |
| Products |
sdk.store.product.retrieve(id) |
Server (detail) |
| Cart |
sdk.store.cart.create() |
Client (interaction) |
| Cart |
sdk.store.cart.addLineItem() |
Client (interaction) |
| Cart |
sdk.store.cart.update() |
Client (interaction) |
| Checkout |
sdk.store.cart.addShippingMethod() |
Client (checkout flow) |
| Customer |
sdk.auth.login() |
Client (auth form) |
| Customer |
sdk.store.customer.retrieve() |
Server or Client |
| Regions |
sdk.store.region.list() |
Server (layout) |
| Collections |
sdk.store.collection.list() |
Server (navigation) |
Data Fetching Patterns
Server Component Fetching
Call the Medusa SDK directly in server components — no hooks needed:
// Fetch live docs for server-side SDK
// usage and async component patterns
| Pattern |
Use Case |
Direct await sdk.store.* |
Server components with async data |
generateMetadata() |
Dynamic SEO metadata from product data |
generateStaticParams() |
ISR/SSG for product and category pages |
Client Component Fetching (Tanstack Query)
| Hook / Concern |
Purpose |
useQuery |
Read data with caching and automatic refetch |
useMutation |
Write operations (add to cart, login) |
useQueryClient |
Invalidate cache after mutations |
| Query keys |
Use consistent keys like ["cart", cartId] |
| Optimistic updates |
Update cart UI immediately, rollback on error |
| Prefetching |
Prefetch product data on hover for navigation |
Caching Strategies
Next.js Caching Layers
| Layer |
Scope |
Medusa Use Case |
| Request Memoization |
Per-request dedup |
Multiple components fetching same product |
| Data Cache |
Cross-request |
Product catalog data (revalidate periodically) |
| Full Route Cache |
Entire page |
Static product pages (ISR) |
| Router Cache |
Client-side |
Navigation between cached pages |
Revalidation Patterns
| Strategy |
Method |
Use Case |
| Time-based |
revalidate: 60 (seconds) |
Product listings, category pages |
| On-demand |
revalidatePath() / revalidateTag() |
After admin product update |
| No cache |
cache: "no-store" |
Cart, checkout, customer data |
Cache Configuration per Data Type
| Data Type |
Cache Strategy |
Reasoning |
| Products |
revalidate: 60-300 |
Changes infrequently |
| Collections |
revalidate: 300-3600 |
Rarely changes |
| Cart |
no-store |
User-specific, changes constantly |
| Customer |
no-store |
Private, per-session |
| Regions |
revalidate: 3600 |
Almost never changes |
| Prices |
revalidate: 60 |
May change with promotions |
Server Actions
Server actions handle form submissions and mutations from server components:
// Fetch live docs for Next.js server actions
// with Medusa SDK mutation patterns
"use server"
| Use Case |
Action Pattern |
| Add to cart |
Server action calling sdk.store.cart.addLineItem() |
| Update quantity |
Server action with revalidatePath |
| Apply discount |
Server action calling sdk.store.cart.update() |
| Customer login |
Server action wrapping sdk.auth.login() |
Use server actions for simple form mutations. Use API routes when external systems need to call your storefront.
Storefront Environment Variables
| Variable |
Purpose |
Where Used |
NEXT_PUBLIC_MEDUSA_BACKEND_URL |
Medusa server URL |
Client + Server |
NEXT_PUBLIC_MEDUSA_PUBLISHABLE_KEY |
Store API key |
Client + Server |
REVALIDATE_WINDOW |
Default ISR revalidation time |
Server only |
Best Practices
- Component boundaries — default to server components; add
"use client" only for interactive elements; split pages into server (data) and client (interaction) sub-components
- SDK usage — initialize once in a shared module; use server-side calls in server components for SEO; use Tanstack Query in client components for reactivity and caching
- Caching discipline — cache product and collection data aggressively; never cache cart or customer data; use on-demand revalidation for admin-triggered updates; monitor cache hit rates
- Performance — use
loading.tsx for streaming with Suspense boundaries; prefetch data for likely navigation targets; optimize images with next/image and Medusa media URLs
Fetch the Medusa Next.js storefront documentation and JS SDK reference for exact method signatures, query key conventions, and starter template patterns before implementing.
1---2name: nextjs-patterns3description: Build Next.js 15 storefronts for Medusa v2 — App Router conventions, server vs client components, Medusa JS SDK integration, data fetching, caching, and server actions. Use when building Medusa storefronts with Next.js.4---56# Next.js 15 Storefront Patterns for Medusa v278## Before writing code910**Fetch live docs**:111. Web-search `site:docs.medusajs.com storefront nextjs` for Medusa storefront guides122. Fetch `https://docs.medusajs.com/resources/references/js-sdk` for JS SDK method reference133. Web-search `site:nextjs.org docs app router` for latest Next.js 15 App Router conventions144. Web-search `site:docs.medusajs.com nextjs starter storefront` for starter template patterns155. Web-search `site:docs.medusajs.com publishable api key storefront` for API authentication setup1617## App Router Conventions1819### Directory Structure2021```22src/app/23├── layout.tsx — Root layout (providers, SDK)24├── page.tsx — Home page25├── (main)/products/ — Product listing + [handle]/26├── (main)/cart/ — Cart, checkout, account27└── lib/medusa.ts — SDK client initialization28```2930### Key App Router Concepts3132| Concept | File Convention | Purpose in Medusa Storefront |33|---------|----------------|------------------------------|34| Layout | `layout.tsx` | Wrap pages with providers, header, footer |35| Page | `page.tsx` | Route entry point (server component default) |36| Loading | `loading.tsx` | Streaming fallback UI |37| Error | `error.tsx` | Error boundary per route segment |38| Not Found | `not-found.tsx` | 404 page for invalid product handles, etc. |39| Route Groups | `(group)/` | Organize without affecting URL |40| Dynamic Routes | `[param]/` | Product handles, category slugs |41| Parallel Routes | `@slot/` | Simultaneous layout regions |4243## Server vs Client Components4445### Decision Matrix4647| Criterion | Server Component | Client Component |48|-----------|-----------------|-----------------|49| **Data fetching** | Fetch from Medusa API directly | Use Tanstack Query hooks |50| **SEO** | Full HTML rendered on server | Not indexed by crawlers |51| **Interactivity** | No event handlers | onClick, onChange, etc. |52| **State** | No `useState`/`useEffect` | Full React hooks |53| **Examples** | Product listing, product detail, categories | Cart actions, quantity selector, search |54| **Directive** | None (default) | `"use client"` at top |5556### Component Split Pattern5758```59ProductPage (server)60├── ProductInfo (server) — Title, description, price61├── ProductImages (server) — Image gallery markup62├── AddToCart (client) — "use client", quantity, button63└── RelatedProducts (server) — Fetched server-side64```6566## Medusa JS SDK Integration6768### SDK Initialization and Configuration6970| Option | Purpose | Example Value |71|--------|---------|---------------|72| `baseUrl` | Medusa server URL | `http://localhost:9000` |73| `publishableKey` | Store API authentication | From admin dashboard |74| `auth.type` | Authentication strategy | `"session"` or `"jwt"` |7576### Common SDK Methods7778| Domain | Method Pattern | Component Type |79|--------|---------------|---------------|80| Products | `sdk.store.product.list()` | Server (listing) |81| Products | `sdk.store.product.retrieve(id)` | Server (detail) |82| Cart | `sdk.store.cart.create()` | Client (interaction) |83| Cart | `sdk.store.cart.addLineItem()` | Client (interaction) |84| Cart | `sdk.store.cart.update()` | Client (interaction) |85| Checkout | `sdk.store.cart.addShippingMethod()` | Client (checkout flow) |86| Customer | `sdk.auth.login()` | Client (auth form) |87| Customer | `sdk.store.customer.retrieve()` | Server or Client |88| Regions | `sdk.store.region.list()` | Server (layout) |89| Collections | `sdk.store.collection.list()` | Server (navigation) |9091## Data Fetching Patterns9293### Server Component Fetching9495Call the Medusa SDK directly in server components — no hooks needed:9697```ts98// Fetch live docs for server-side SDK99// usage and async component patterns100```101102| Pattern | Use Case |103|---------|----------|104| Direct `await sdk.store.*` | Server components with async data |105| `generateMetadata()` | Dynamic SEO metadata from product data |106| `generateStaticParams()` | ISR/SSG for product and category pages |107108### Client Component Fetching (Tanstack Query)109110| Hook / Concern | Purpose |111|----------------|---------|112| `useQuery` | Read data with caching and automatic refetch |113| `useMutation` | Write operations (add to cart, login) |114| `useQueryClient` | Invalidate cache after mutations |115| Query keys | Use consistent keys like `["cart", cartId]` |116| Optimistic updates | Update cart UI immediately, rollback on error |117| Prefetching | Prefetch product data on hover for navigation |118119## Caching Strategies120121### Next.js Caching Layers122123| Layer | Scope | Medusa Use Case |124|-------|-------|-----------------|125| **Request Memoization** | Per-request dedup | Multiple components fetching same product |126| **Data Cache** | Cross-request | Product catalog data (revalidate periodically) |127| **Full Route Cache** | Entire page | Static product pages (ISR) |128| **Router Cache** | Client-side | Navigation between cached pages |129130### Revalidation Patterns131132| Strategy | Method | Use Case |133|----------|--------|----------|134| Time-based | `revalidate: 60` (seconds) | Product listings, category pages |135| On-demand | `revalidatePath()` / `revalidateTag()` | After admin product update |136| No cache | `cache: "no-store"` | Cart, checkout, customer data |137138### Cache Configuration per Data Type139140| Data Type | Cache Strategy | Reasoning |141|-----------|---------------|-----------|142| Products | `revalidate: 60-300` | Changes infrequently |143| Collections | `revalidate: 300-3600` | Rarely changes |144| Cart | `no-store` | User-specific, changes constantly |145| Customer | `no-store` | Private, per-session |146| Regions | `revalidate: 3600` | Almost never changes |147| Prices | `revalidate: 60` | May change with promotions |148149## Server Actions150151Server actions handle form submissions and mutations from server components:152153```ts154// Fetch live docs for Next.js server actions155// with Medusa SDK mutation patterns156"use server"157```158159| Use Case | Action Pattern |160|----------|---------------|161| Add to cart | Server action calling `sdk.store.cart.addLineItem()` |162| Update quantity | Server action with `revalidatePath` |163| Apply discount | Server action calling `sdk.store.cart.update()` |164| Customer login | Server action wrapping `sdk.auth.login()` |165166Use server actions for simple form mutations. Use API routes when external systems need to call your storefront.167168## Storefront Environment Variables169170| Variable | Purpose | Where Used |171|----------|---------|-----------|172| `NEXT_PUBLIC_MEDUSA_BACKEND_URL` | Medusa server URL | Client + Server |173| `NEXT_PUBLIC_MEDUSA_PUBLISHABLE_KEY` | Store API key | Client + Server |174| `REVALIDATE_WINDOW` | Default ISR revalidation time | Server only |175176## Best Practices177178- **Component boundaries** — default to server components; add `"use client"` only for interactive elements; split pages into server (data) and client (interaction) sub-components179- **SDK usage** — initialize once in a shared module; use server-side calls in server components for SEO; use Tanstack Query in client components for reactivity and caching180- **Caching discipline** — cache product and collection data aggressively; never cache cart or customer data; use on-demand revalidation for admin-triggered updates; monitor cache hit rates181- **Performance** — use `loading.tsx` for streaming with Suspense boundaries; prefetch data for likely navigation targets; optimize images with `next/image` and Medusa media URLs182183Fetch the Medusa Next.js storefront documentation and JS SDK reference for exact method signatures, query key conventions, and starter template patterns before implementing.