Setting the file. One moment.
Connecting To Backend · Storefront Best Practices · medusajs/medusa-agent-skills · Skills Docs
ContentsBack to the top of the page Mcloud
Best practices for connecting storefront to ecommerce backend APIs. Framework-agnostic patterns for authentication, cart state management, error handling, and performance optimization.
For Medusa-specific integration , see reference/medusa.md for SDK setup, pricing, regions, and Medusa patterns.
Before implementing any backend integration, identify which ecommerce backend is being used.
1. Check for monorepo structure:
Common monorepo patterns:
/apps/storefront + /apps/backend
/frontend + /backend
/packages/web + /packages/api
2. Check package.json dependencies:
3. Check environment variables:
NEXT_PUBLIC_MEDUSA_BACKEND_URL → Medusa
Custom API_URL or BACKEND_URL → Other backend
4. If unsure, ASK THE USER:
ALWAYS refer to the backend’s official documentation or MCP server for:
API endpoints and data structures
Authentication requirements
SDK usage and installation
Environment configuration
Rate limits and best practices
Check the backend’s documentation portal
Look for MCP server if available
Verify API endpoints and authentication methods
Never assume API structure without verification
Important: Do not guess API endpoints or data formats. Always verify with documentation or ask the user to confirm the backend’s API structure.
Identify the frontend framework to determine appropriate data fetching patterns:
App Router: Server Components (async/await), Client Components (useEffect/TanStack Query)
Pages Router: getServerSideProps/getStaticProps (server), useEffect (client)
Load functions for server-side data
Client-side: fetch in component lifecycle
Server functions for server-side data
Client-side: fetch with React hooks
Server-side for initial load : SEO, performance, security (product pages, listings)
Client-side for interactions : Cart, filters, search, user-specific data
Store API URLs and keys in environment variables:
Framework-specific prefixes:
Next.js: NEXT_PUBLIC_ for client-side
SvelteKit: PUBLIC_ for client-side
Vite-based (TanStack Start): VITE_ for client-side
❌ NEVER expose secret/admin keys in client-side code
✅ Publishable keys are safe for client (Medusa, Stripe)
✅ Secret keys only in server-side code or environment
For complete Medusa integration guide , see reference/medusa.md which covers:
SDK installation and setup
Vite configuration (for TanStack Start, etc.)
TypeScript types from @medusajs/types
Price display (never divide by 100)
Common operations (products, cart, categories, customers)
Custom endpoints
Region state management
Error handling with SDK
For non-Medusa backends (custom APIs, third-party platforms):
1. Consult backend’s API documentation for:
Authentication requirements
Available endpoints
Request/response formats
SDK availability (check if official SDK exists)
2. Use backend’s official SDK if available - provides type safety, error handling, and best practices
3. If no SDK, create API client wrapper:
Centralize API calls in one module
Group by resource (products, cart, customers, orders)
Handle authentication (include tokens/cookies)
Handle errors consistently
Use native fetch or axios
Backend manages session via cookies
No manual token management needed
Works across page refreshes
Common in traditional ecommerce backends
Call backend login endpoint, check auth state, logout methods
Token-based (JWT, OAuth):
Store token in localStorage or secure cookie after login
Include token in Authorization header for all authenticated requests
Common in headless/API-first backends
Format: Authorization: Bearer {token}
Check authentication before rendering customer-specific pages (account, orders, addresses):
Server-side : Check auth in server functions (getServerSideProps, load functions, etc.). Redirect to login if not authenticated.
Client-side : Check auth state on mount. Redirect to login if not authenticated.
Use framework-specific auth patterns for redirects.
Store cart ID in localStorage or cookie
Check for existing cart ID on app load
Create new cart if none exists
Allows shopping without account
Persists across sessions
Associate cart with customer account
Syncs across devices
CRITICAL: Merge guest cart with customer cart on login - Transfer guest cart items to customer’s account cart, then clear guest cart ID from localStorage
Critical ecommerce pattern : Cart must be accessible throughout the app.
React Context (for simple cases):
Create CartContext and CartProvider
Store cart state and cartId (from localStorage)
Load cart on mount if cartId exists
Provide methods: addItem, removeItem, updateQuantity, clearCart
Update cart state after each operation
State management libraries (Zustand, Redux):
Use for complex state requirements
Better for large applications
Easier to debug with DevTools
Same pattern: Store cart, provide actions, sync with backend
Cart accessible from any component
Real-time cart count updates
Optimistic UI updates (update UI immediately, sync with backend)
IMPORTANT: After order is successfully placed, you MUST reset the cart state.
Common issue: Cart popup and global cart state still show old items after order completion. This happens when cart state isn’t cleared after checkout.
Required cleanup actions:
Clear cart from global state - Reset cart state to null/empty in Context/Zustand/Redux
Clear localStorage cart ID - Remove cart ID: localStorage.removeItem('cart_id')
Invalidate cart queries - If using TanStack Query: queryClient.invalidateQueries({ queryKey: ['cart'] })
Update cart count to 0 - Navbar and UI should reflect empty cart
After successful order placement (order confirmed)
On navigation to order confirmation page
Before redirecting to thank you page
Prevents “phantom cart” from appearing in cart popup after order
Ensures clean state for next shopping session
Improves UX by not showing old cart items
Catch errors when adding to cart
Check for “out of stock” or “inventory” in error message
Show user-friendly message: “Sorry, this item is now out of stock”
Update product availability UI to show out of stock
Price changed during checkout:
Compare cart total with expected total
If different, show warning: “Prices have been updated. Please review your cart.”
Highlight changed prices in cart
Catch errors during order completion
Check for specific payment errors: payment_declined, insufficient_funds, etc.
Show specific messages:
Payment declined → “Payment declined. Please try a different payment method.”
Insufficient funds → “Insufficient funds. Please use a different card.”
Generic → “Payment failed. Please try again or contact support.”
Catch 401/Unauthorized errors
Clear auth state
Redirect to login with message: “Your session has expired. Please log in again.”
Transform technical errors to clear messages:
Network/fetch errors → “Unable to connect. Please check your internet connection.”
Timeout errors → “Request timed out. Please try again.”
Inventory errors → “This item is no longer available in the requested quantity.”
Generic fallback → “Something went wrong. Please try again or contact support.”
Pattern : Check error message or status code, map to user-friendly message, show in UI (toast, banner, inline).
Use TanStack Query for all backend API calls - provides automatic caching, request deduplication, loading/error states, and optimistic updates.
Installation: npm install @tanstack/react-query
Create QueryClient with default options (staleTime: 5 min, retry: 1)
Wrap app with QueryClientProvider
Query pattern (for fetching data):
Use useQuery with queryKey and queryFn
queryKey: Array with resource and identifier ['products', categoryId]
queryFn: API call function
Returns: data, isLoading, error
Use for: Products, cart, customer data, categories
Mutation pattern (for modifying data):
Use useMutation with mutationFn
mutationFn: API operation (add to cart, update, delete)
onSuccess: Update cache or invalidate queries
Returns: mutate function, isPending state
Use for: Add to cart, remove from cart, update quantities, place order
Automatic caching (no manual cache management)
Built-in loading/error states
Request deduplication
Optimistic updates (update UI before server responds)
Cache invalidation strategies
Ecommerce-specific usage:
Products: Long stale time (5-10 min) - products don’t change often
Cart: Short or no stale time - prices/inventory can change
Categories: Long stale time - rarely change
TanStack Query handles automatically with staleTime and cacheTime
Configure globally or per-query
Product data: 5-10 min stale time
Cart data: Fresh on every fetch
Categories: Long stale time
Server-side caching (framework-specific):
Next.js: Use revalidate export or cache configuration
Set revalidation period (e.g., 300 seconds for product pages)
Static generation with ISR for product pages
TanStack Query and modern frameworks handle this automatically - multiple components requesting same data result in single request.
Offset-based: Pass limit and offset parameters to API limit: 24, offset: page * 24
Cursor-based (better performance): Pass limit and cursor (last item ID) limit: 24, cursor: lastProductId
Check backend documentation for supported pagination type.
Essential backend integration:
Not done: Backend detected (Medusa, Shopify, custom, etc.)
Not done: Environment variables configured (API URL, keys)
Not done: Framework-specific data fetching patterns identified
Not done: RECOMMENDED: TanStack Query installed and configured for API calls
Not done: Server-side fetching for product pages (SEO)
Not done: Client-side fetching for cart and user interactions (use TanStack Query)
Not done: Authentication flow implemented (login/logout)
Not done: Cart ID persisted in localStorage or cookies
Not done: Global cart state management (context or store)
Not done: Cart count synced across app
Not done: Optimistic UI updates for cart operations
Not done: Error handling for out of stock scenarios
Not done: Error handling for payment failures
Not done: Session expiration handling (redirect to login)
Not done: User-friendly error messages (not technical)
Not done: Caching strategy for product data
Not done: Stock availability checks before checkout
Not done: Price change detection and warnings
For Medusa backends, also check:
Not done: Medusa SDK installed (@medusajs/js-sdk + @medusajs/types)
Not done: SDK initialized with baseUrl and publishableKey
Not done: Vite SSR config added (if using TanStack Start/Vite)
Not done: Using official types from @medusajs/types
Not done: Not dividing prices by 100 (display as-is)
Not done: Region context implemented for multi-region stores
Not done: Region passed to cart and product queries
See reference/medusa.md for complete Medusa integration guide.
Reference Design