diff --git a/www/apps/resources/app/storefront-development/production-optimizations/page.mdx b/www/apps/resources/app/storefront-development/production-optimizations/page.mdx
new file mode 100644
index 0000000000..6440b32805
--- /dev/null
+++ b/www/apps/resources/app/storefront-development/production-optimizations/page.mdx
@@ -0,0 +1,1217 @@
+---
+tags:
+ - storefront
+---
+
+import { Table, CodeTabs } from "docs-ui"
+
+export const metadata = {
+ title: `Storefront Production Optimization Tips`,
+}
+
+# {metadata.title}
+
+In this guide, you’ll find tips useful when optimizing a storefront for production.
+
+## Summary
+
+When building a storefront, you want to ensure that it loads quickly and efficiently for your users. You can achieve this by implementing many optimizations, including:
+
+1. [Implement appropriate rendering strategies](#choose-the-right-rendering-strategy).
+2. [Use fetching libraries optimized for performance and caching like TanStack Query](#use-tanstack-query).
+3. [Optimize queries to fetch only the necessary data](#optimize-fetched-data).
+4. [Implement optimistic UI updates for cart operations](#implement-optimistic-ui-updates).
+
+This guide explains how to implement these optimizations. You can follow it regardless of the frontend framework you use.
+
+
+
+There are other important optimizations like lazy-loading images, code-splitting, and using CDNs. These optimizations depend on the frontend framework you use and your setup. This guide focuses on Medusa-specific optimizations in your storefront.
+
+
+
+---
+
+## Choose the Right Rendering Strategy
+
+A rendering strategy defines how your frontend framework renders pages. The most common strategies are:
+
+1. **Server-Side Rendering (SSR)**: Pages are rendered on the server for each request. Ideal for dynamic content that changes frequently.
+2. **Static Site Generation (SSG)**: Pages are pre-rendered at build time. Best suited for content that doesn't change often.
+3. **Incremental Static Regeneration (ISR)**: A hybrid approach where pages are pre-rendered at build time but can be updated at runtime. Perfect for content that changes occasionally.
+4. **Client-Side Rendering (CSR)**: Pages are rendered in the browser using JavaScript. Optimal for highly interactive applications.
+
+For your storefront, we recommend using different strategies for different pages:
+
+
+
+
+
+ Page Type
+
+
+ Recommended Strategy
+
+
+
+
+
+
+ Homepage
+
+
+ SSG above the fold, CSR below the fold. For example, render the hero section at build time and load product recommendations client-side.
+
+
+
+
+ Product Listing Page (PLP)
+
+
+ ISR or CSR.
+
+
+
+
+ Product Detail Page (PDP)
+
+
+ SSG for product content that doesn't change often, such as descriptions and images. Use CSR for dynamic content like stock availability and prices.
+
+
+
+
+ Cart and Checkout Pages
+
+
+ CSR.
+
+
+
+
+ Blog or Content Pages (About Us, Privacy Policy, etc...)
+
+
+ SSG or ISR.
+
+
+
+
+ User Account Pages
+
+
+ CSR.
+
+
+
+
+
+How you implement these strategies depends on the frontend framework you use. Refer to the documentation of your framework for guidance.
+
+---
+
+## Use TanStack Query
+
+When fetching data from your Medusa backend, consider using a fetching library optimized for performance and caching. There are many options available, but one of the most popular is [TanStack Query](https://tanstack.com/query/latest).
+
+TanStack Query is a powerful data-fetching library that provides features like caching, background updates, and optimistic updates.
+
+By using TanStack Query, you can significantly improve your storefront's performance by reducing the number of network requests and ensuring that your UI is always up-to-date.
+
+Learn how to get started with TanStack Query in their [official documentation](https://tanstack.com/query/latest/docs/framework/react/installation). Their documentation also has guidance on advanced usage and best practices.
+
+### Stale Time Configuration
+
+When configuring TanStack Query, you can set the `staleTime` option to control how long data is considered fresh. However, avoid setting `staleTime` for highly dynamic data like product prices and stock levels.
+
+Set the `staleTime` globally, then override it to `0` for specific queries that fetch dynamic data.
+
+For example:
+
+```ts
+// queryClient.ts
+import { QueryClient } from "@tanstack/react-query"
+
+const queryClient = new QueryClient({
+ defaultOptions: {
+ queries: {
+ staleTime: 1000 * 60 * 5, // 5 minutes
+ },
+ },
+});
+
+export default queryClient;
+
+// product.ts
+// Used in components that are children of QueryClientProvider
+import { useQuery } from "@tanstack/react-query"
+import { sdk } from "../lib/sdk"
+
+export const useProduct = (id: string) => {
+ return useQuery(["product", id], async () => {
+ const response = await sdk.store.products.retrieve(id);
+ return response.data;
+ });
+};
+
+export const useProductPrice = (id: string) => {
+ return useQuery(
+ ["product-price", id],
+ async () => {
+ const response = await sdk.store.products.retrieve(id, {
+ fields: "*variants.calculated_price",
+ });
+ return response.data.variants[0].price;
+ },
+ {
+ staleTime: 0, // Always fetch fresh data
+ }
+ );
+};
+```
+
+### Invalidate Queries
+
+When performing mutations that change cached data, invalidate the relevant queries to ensure that the UI reflects the latest data.
+
+For example, when adding an item to the cart, you should invalidate the cart query:
+
+```ts
+import { useMutation, useQueryClient } from "@tanstack/react-query"
+import { sdk } from "../lib/sdk"
+
+export const useAddToCart = () => {
+ const queryClient = useQueryClient();
+
+ return useMutation(
+ async (item) => {
+ await sdk.store.cart.createLineItem("cart_id", item);
+ },
+ {
+ onSuccess: () => {
+ queryClient.invalidateQueries(["cart"]);
+ },
+ }
+ );
+};
+```
+
+---
+
+## Optimize Fetched Data
+
+When fetching data from your Medusa backend, optimize your queries to fetch only the necessary data in the context of a component or page. This reduces the response size and time, improving your storefront's performance.
+
+Medusa's API routes accept a `fields` parameter that allows you to specify which fields and relations to include in the response.
+
+For example, if you only need the product's `id`, `title`, and `variants.calculated_price`, you can specify this in the `fields` parameter:
+
+```ts
+const response = await sdk.store.products.retrieve("product_id", {
+ fields: "*variants.calculated_price, id, title",
+});
+```
+
+This query will return only the specified fields, reducing the response size and improving performance.
+
+If you're using TanStack Query, make sure to include the `fields` parameter in the query key to ensure that different field selections are cached separately:
+
+```ts
+const useProduct = (id: string, fields?: string) => {
+ return useQuery({
+ queryKey: ["product", id, fields],
+ queryFn: async () => {
+ const response = await sdk.store.products.retrieve(id, { fields });
+ return response.data;
+ },
+ });
+};
+```
+
+Learn more about the `fields` parameter in the [Store API reference](!api!/store#select-fields-and-relations).
+
+---
+
+## Implement Optimistic UI Updates
+
+When performing mutations that may take some time, you can implement optimistic UI updates to provide a better user experience. This means updating the UI immediately, assuming the mutation will succeed, then rolling back if it fails.
+
+For example, if you're using TanStack Query, you can implement the following optimistic cart-update utilities and then use them in cart mutations:
+
+
+
+The following code snippets are not complete implementations. They are simplified for clarity. They assume that:
+
+- You defined separate cart data functions that use the [JS SDK](../../js-sdk/page.mdx) to call the Medusa backend.
+- You have a `query-keys.ts` utility that defines consistent query keys for TanStack Query.
+
+You can make changes as needed based on your implementation.
+
+
+
+
+
+
+```ts title="optimistic-cart.ts" collapsibleLines="60-383" expandButtonLabel="Show all code"
+import { HttpTypes } from "@medusajs/types"
+import { QueryClient } from "@tanstack/react-query"
+import { queryKeys } from "@/lib/utils/common/query-keys"
+
+/**
+ * Utility functions for optimistic cart updates
+ */
+
+export interface OptimisticCartItem {
+ id: string;
+ variant_id: string;
+ quantity: number;
+ title: string;
+ thumbnail?: string | null;
+ product_title?: string;
+ variant_title?: string;
+ product?: {
+ id: string;
+ title: string;
+ };
+ variant?: {
+ id: string;
+ title: string;
+ };
+ unit_price: number;
+ total: number;
+ isOptimistic?: boolean;
+}
+
+export interface OptimisticCart extends HttpTypes.StoreCart {
+ isOptimistic?: boolean;
+}
+
+/**
+ * Creates an optimistic cart item for immediate UI updates during add to cart operations.
+ * Generates a temporary item with calculated pricing before the server response.
+ *
+ * @param variant - The product variant being added to cart
+ * @param product - The product object containing title and thumbnail
+ * @param quantity - The quantity to add (defaults to 1)
+ * @returns Optimistic cart item with temporary ID and calculated totals
+ *
+ * @example
+ * ```typescript
+ * const optimisticItem = createOptimisticCartItem(variant, product, 2);
+ * // Returns item with temporary ID and calculated price for immediate UI update
+ * ```
+ */
+export const createOptimisticCartItem = (
+ variant: HttpTypes.StoreProductVariant,
+ product: HttpTypes.StoreProduct,
+ quantity: number = 1
+): OptimisticCartItem => {
+ const unitPrice = variant.calculated_price?.calculated_amount || 0
+
+ return {
+ id: `optimistic-${variant.id}-${Date.now()}`, // Temporary ID
+ variant_id: variant.id,
+ quantity,
+ title: product.title,
+ thumbnail: product.thumbnail,
+ product: {
+ id: product.id,
+ title: product.title,
+ },
+ product_title: product.title,
+ variant: {
+ id: variant.id,
+ title: variant.title || "Default Variant",
+ },
+ variant_title: variant.title || "Default Variant",
+ unit_price: unitPrice,
+ total: unitPrice * quantity,
+ isOptimistic: true,
+ }
+}
+
+/**
+ * Adds an item to the cart optimistically by updating the query cache immediately.
+ * This provides instant UI feedback while the actual API call is in progress.
+ *
+ * @param queryClient - TanStack Query client for cache management
+ * @param newItem - The optimistic cart item to add
+ * @param fields - Optional fields parameter for query key
+ * @returns Updated cart object or null if no current cart exists
+ *
+ * @example
+ * ```typescript
+ * const updatedCart = addItemOptimistically(queryClient, optimisticItem);
+ * if (updatedCart) {
+ * // UI immediately shows the new item
+ * // Real API call happens in background
+ * }
+ * ```
+ */
+export const addItemOptimistically = (
+ queryClient: QueryClient,
+ newItem: OptimisticCartItem,
+ optimisticCart?: OptimisticCart,
+ fields?: string
+): HttpTypes.StoreCart | null => {
+ const currentCart = optimisticCart || queryClient.getQueryData(
+ queryKeys.cart.current(fields)
+ )
+
+ if (!currentCart) {
+ // If no cart exists, we can't add optimistically
+ // The mutation will handle creating a new cart
+ return null
+ }
+
+ // Check if item already exists in cart
+ const existingItemIndex = currentCart.items?.findIndex(
+ item => item.variant_id === newItem.variant_id
+ )
+
+ let updatedItems: HttpTypes.StoreCartLineItem[]
+
+ if (existingItemIndex !== undefined && existingItemIndex >= 0) {
+ // Update existing item quantity
+ updatedItems = [...(currentCart.items || [])]
+ const existingItem = updatedItems[existingItemIndex]
+ updatedItems[existingItemIndex] = {
+ ...existingItem,
+ quantity: existingItem.quantity + newItem.quantity,
+ total: (existingItem.unit_price || 0) * (existingItem.quantity + newItem.quantity),
+ }
+ } else {
+ // Add new item - cast to StoreCartLineItem for compatibility
+ const optimisticLineItem = {
+ ...newItem,
+ cart_id: currentCart.id,
+ cart: currentCart,
+ item_total: newItem.total,
+ item_subtotal: newItem.total,
+ item_tax_total: 0,
+ original_total: newItem.total,
+ original_tax_total: 0,
+ original_subtotal: newItem.total,
+ discount_total: 0,
+ discount_tax_total: 0,
+ gift_card_total: 0,
+ subtotal: newItem.total,
+ tax_total: 0,
+ total: newItem.total,
+ created_at: new Date(),
+ updated_at: new Date(),
+ metadata: {},
+ adjustments: [],
+ tax_lines: [],
+ unit_tax_amount: 0,
+ requires_shipping: true,
+ is_discountable: true,
+ is_tax_inclusive: false,
+ } as HttpTypes.StoreCartLineItem
+
+ updatedItems = [...(currentCart.items || []), optimisticLineItem]
+ }
+
+ const newItemSubtotal = updatedItems.reduce((sum, item) => sum + (item.total || 0), 0)
+
+ const newOptimisticCart: OptimisticCart = {
+ ...currentCart,
+ items: updatedItems,
+ item_subtotal: newItemSubtotal,
+ isOptimistic: true,
+ }
+
+ // Update the cache optimistically
+ queryClient.setQueryData(queryKeys.cart.current(fields), newOptimisticCart)
+
+ return newOptimisticCart
+}
+
+/**
+ * Updates a cart line item quantity optimistically in the query cache.
+ * Provides immediate UI feedback for quantity changes.
+ *
+ * @param queryClient - TanStack Query client for cache management
+ * @param lineId - The ID of the line item to update
+ * @param quantity - The new quantity for the line item
+ * @param fields - Optional fields parameter for query key
+ * @returns Updated cart object or null if no current cart exists
+ *
+ * @example
+ * ```typescript
+ * const updatedCart = updateLineItemOptimistically(queryClient, "line_123", 3);
+ * if (updatedCart) {
+ * // UI immediately shows updated quantity and totals
+ * }
+ * ```
+ */
+export const updateLineItemOptimistically = (
+ queryClient: QueryClient,
+ lineId: string,
+ quantity: number,
+ fields?: string
+): HttpTypes.StoreCart | null => {
+ const currentCart = queryClient.getQueryData(
+ queryKeys.cart.current(fields)
+ )
+
+ if (!currentCart) {
+ return null
+ }
+
+ const updatedItems = (currentCart.items || []).map(item => {
+ if (item.id === lineId) {
+ return {
+ ...item,
+ quantity,
+ total: (item.unit_price || 0) * quantity,
+ original_total: (item.unit_price || 0) * quantity,
+ }
+ }
+ return item
+ })
+
+ const optimisticCart: OptimisticCart = {
+ ...currentCart,
+ items: updatedItems,
+ item_subtotal: updatedItems.reduce((sum, item) => sum + (item.total || 0), 0),
+ isOptimistic: true,
+ }
+
+ queryClient.setQueryData(queryKeys.cart.current(fields), optimisticCart)
+
+ return optimisticCart
+}
+
+/**
+ * Removes a cart line item optimistically from the query cache.
+ * Provides immediate UI feedback for item removal.
+ *
+ * @param queryClient - TanStack Query client for cache management
+ * @param lineId - The ID of the line item to remove
+ * @param fields - Optional fields parameter for query key
+ * @returns Updated cart object or null if no current cart exists
+ *
+ * @example
+ * ```typescript
+ * const updatedCart = removeLineItemOptimistically(queryClient, "line_123");
+ * if (updatedCart) {
+ * // UI immediately shows item removed and updated totals
+ * }
+ * ```
+ */
+export const removeLineItemOptimistically = (
+ queryClient: QueryClient,
+ lineId: string,
+ fields?: string
+): HttpTypes.StoreCart | null => {
+ const currentCart = queryClient.getQueryData(
+ queryKeys.cart.current(fields)
+ )
+
+ if (!currentCart) {
+ return null
+ }
+
+ const updatedItems = (currentCart.items || []).filter(item => item.id !== lineId)
+
+ const optimisticCart: OptimisticCart = {
+ ...currentCart,
+ items: updatedItems,
+ item_subtotal: updatedItems.reduce((sum, item) => sum + (item.total || 0), 0),
+ isOptimistic: true,
+ }
+
+ queryClient.setQueryData(queryKeys.cart.current(fields), optimisticCart)
+
+ return optimisticCart
+}
+
+/**
+ * Rolls back optimistic cart changes when an API call fails.
+ * Restores the cart to its previous state before the optimistic update.
+ *
+ * @param queryClient - TanStack Query client for cache management
+ * @param previousCart - The cart state to restore to
+ * @param fields - Optional fields parameter for query key
+ *
+ * @example
+ * ```typescript
+ * try {
+ * await addToCart(variant);
+ * } catch (error) {
+ * // Rollback optimistic changes on error
+ * rollbackOptimisticCart(queryClient, previousCart);
+ * showErrorMessage("Failed to add item to cart");
+ * }
+ * ```
+ */
+export const rollbackOptimisticCart = (
+ queryClient: QueryClient,
+ previousCart: HttpTypes.StoreCart | null,
+ fields?: string
+) => {
+ queryClient.setQueryData(queryKeys.cart.current(fields), previousCart)
+}
+
+/**
+ * Creates an optimistic cart for immediate UI updates during cart creation operations.
+ * Generates a temporary cart with basic structure before the server response.
+ *
+ * @param region_id - The region ID for the cart
+ * @param fields - Optional fields parameter for query key
+ * @returns Optimistic cart with temporary ID and basic structure
+ *
+ * @example
+ * ```typescript
+ * const optimisticCart = createOptimisticCart('reg_us');
+ * // Returns cart with temporary ID for immediate UI update
+ * ```
+ */
+export const createOptimisticCart = (region: HttpTypes.StoreRegion): OptimisticCart => {
+ const tempId = `optimistic-cart-${Date.now()}`
+
+ return {
+ id: tempId,
+ region_id: region.id,
+ items: [],
+ item_subtotal: 0,
+ item_tax_total: 0,
+ item_total: 0,
+ original_item_total: 0,
+ original_item_tax_total: 0,
+ original_item_subtotal: 0,
+ original_total: 0,
+ original_tax_total: 0,
+ original_subtotal: 0,
+ subtotal: 0,
+ tax_total: 0,
+ total: 0,
+ discount_total: 0,
+ discount_tax_total: 0,
+ gift_card_total: 0,
+ gift_card_tax_total: 0,
+ shipping_total: 0,
+ shipping_tax_total: 0,
+ shipping_subtotal: 0,
+ original_shipping_total: 0,
+ original_shipping_subtotal: 0,
+ original_shipping_tax_total: 0,
+ shipping_address: undefined,
+ billing_address: undefined,
+ shipping_methods: [],
+ payment_collection: undefined,
+ region: undefined,
+ customer_id: undefined,
+ sales_channel_id: undefined,
+ promotions: [],
+ currency_code: region.currency_code,
+ metadata: {},
+ created_at: new Date(),
+ updated_at: new Date(),
+ isOptimistic: true,
+ }
+}
+
+/**
+ * Gets the current cart state from the query cache.
+ * First tries to get the cart with specific fields, then falls back to any cart query.
+ *
+ * @param queryClient - TanStack Query client for cache management
+ * @param fields - Optional fields parameter for query key
+ * @returns Current cart object or null if no cart found in cache
+ *
+ * @example
+ * ```typescript
+ * const currentCart = getCurrentCart(queryClient);
+ * if (currentCart) {
+ * // Use current cart state
+ * console.log(`Cart has ${currentCart.items?.length} items`);
+ * }
+ * ```
+ */
+export const getCurrentCart = (queryClient: QueryClient, fields?: string): HttpTypes.StoreCart | null => {
+ return queryClient.getQueryData(queryKeys.cart.current(fields)) ||
+ queryClient.getQueriesData({
+ predicate: queryKeys.cart.predicate
+ })[0]?.[1] || null
+}
+```
+
+
+
+
+```ts title="use-cart.ts" collapsibleLines="60-567" expandButtonLabel="Show all code"
+import {
+ addToCart,
+ applyPromoCode,
+ createCart,
+ deleteLineItem,
+ removePromoCode,
+ retrieveCart,
+ updateCart,
+ updateLineItem,
+} from "@/lib/data/cart"
+import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"
+import { queryKeys } from "@/lib/utils/common/query-keys"
+import {
+ addItemOptimistically,
+ createOptimisticCartItem,
+ getCurrentCart,
+ rollbackOptimisticCart,
+ updateLineItemOptimistically,
+ removeLineItemOptimistically,
+ createOptimisticCart,
+} from "@/lib/utils/cart/optimistic-cart"
+import { HttpTypes } from "@medusajs/types"
+
+/**
+ * React hook to fetch the current cart with optimistic updates and caching.
+ * Uses Tanstack Query with no stale time to ensure fresh data.
+ *
+ * @param fields - Optional fields to include in the cart response
+ * @returns Tanstack Query result object with cart data, loading, and error states
+ *
+ * @example
+ * ```typescript
+ * // Basic usage
+ * const { data: cart, isLoading, error } = useCart();
+ *
+ * // With specific fields
+ * const { data: cart } = useCart({
+ * fields: '*items, *items.variant, *items.variant.product, shipping_methods'
+ * });
+ *
+ * // In a component
+ * function CartSummary() {
+ * const { data: cart, isLoading } = useCart();
+ *
+ * if (isLoading) return
Loading cart...
;
+ * if (!cart) return
No cart found
;
+ *
+ * return (
+ *
+ *
Cart ({cart.items.length} items)
+ *
Total: {cart.total}
+ *
+ * );
+ * }
+ * ```
+ */
+export const useCart = ({ fields }: { fields?: string } = {}) => {
+ return useQuery({
+ queryKey: queryKeys.cart.current(fields),
+ queryFn: () => retrieveCart({ fields }),
+ staleTime: 0
+ })
+}
+
+/**
+ * React hook to update the cart with automatic cache invalidation.
+ * Uses Tanstack Query's useMutation for handling cart updates.
+ *
+ * @returns Tanstack Query mutation object with mutate function and state
+ *
+ * @example
+ * ```typescript
+ * // Basic usage
+ * const updateCartMutation = useUpdateCart();
+ *
+ * // Usage in component
+ * function UpdateCartButton() {
+ * const updateCartMutation = useUpdateCart();
+ *
+ * const handleUpdateCart = () => {
+ * updateCartMutation.mutate({
+ * region_id: 'reg_us'
+ * }, {
+ * onSuccess: (cart) => {
+ * console.log('Cart updated:', cart);
+ * },
+ * onError: (error) => {
+ * console.error('Failed to update cart:', error);
+ * }
+ * });
+ * };
+ *
+ * return (
+ *
+ * );
+ * }
+ * ```
+ */
+export const useUpdateCart = () => {
+ const queryClient = useQueryClient()
+ return useMutation({
+ mutationFn: updateCart,
+ onSuccess: () => {
+ queryClient.invalidateQueries({ predicate: queryKeys.cart.predicate })
+ }
+ })
+}
+
+/**
+ * React hook to create a new cart with automatic cache invalidation.
+ * Uses Tanstack Query's useMutation for handling cart creation.
+ *
+ * @returns Tanstack Query mutation object with mutate function and state
+ *
+ * @example
+ * ```typescript
+ * // Basic usage
+ * const createCartMutation = useCreateCart();
+ *
+ * // Usage in component
+ * function CreateCartButton() {
+ * const createCartMutation = useCreateCart();
+ *
+ * const handleCreateCart = () => {
+ * createCartMutation.mutate(
+ * { region_id: 'reg_us' },
+ * {
+ * onSuccess: (cart) => {
+ * console.log('Cart created:', cart.id);
+ * // Redirect to cart page
+ * },
+ * onError: (error) => {
+ * console.error('Failed to create cart:', error);
+ * }
+ * }
+ * );
+ * };
+ *
+ * return (
+ *
+ * );
+ * }
+ * ```
+ */
+export const useCreateCart = () => {
+ const queryClient = useQueryClient()
+
+ return useMutation({
+ mutationFn: createCart,
+ onSuccess: () => {
+ queryClient.invalidateQueries({ predicate: queryKeys.cart.predicate })
+ },
+ })
+}
+
+/**
+ * React hook to add items to cart with optimistic updates.
+ * Provides immediate UI feedback while the request is in progress.
+ *
+ * @param fields - Optional fields to include in the cart response
+ * @returns Tanstack Query mutation object with mutate function and state
+ *
+ * @example
+ * ```typescript
+ * // Basic usage
+ * const addToCartMutation = useAddToCart();
+ *
+ * // Usage in component
+ * function AddToCartButton({ variant, product }) {
+ * const addToCartMutation = useAddToCart();
+ *
+ * const handleAddToCart = () => {
+ * addToCartMutation.mutate({
+ * variant_id: variant.id,
+ * quantity: 1,
+ * country_code: 'us',
+ * product,
+ * variant
+ * }, {
+ * onSuccess: (cart) => {
+ * console.log('Added to cart:', cart);
+ * // Show success message
+ * },
+ * onError: (error) => {
+ * console.error('Failed to add to cart:', error);
+ * // Show error message
+ * }
+ * });
+ * };
+ *
+ * return (
+ *
+ * );
+ * }
+ *
+ * // With custom fields
+ * const addToCartWithFields = useAddToCart({
+ * fields: '*items, *items.variant, shipping_methods'
+ * });
+ * ```
+ */
+export const useAddToCart = ({ fields }: { fields?: string } = {}) => {
+ const queryClient = useQueryClient()
+
+ return useMutation({
+ mutationFn: (variables) => addToCart({ ...variables, fields }),
+ onMutate: async (variables: {
+ variant_id: string;
+ quantity: number;
+ country_code: string;
+ product?: HttpTypes.StoreProduct;
+ variant?: HttpTypes.StoreProductVariant;
+ region?: HttpTypes.StoreRegion;
+ }) => {
+ // Cancel any outgoing refetches
+ await queryClient.cancelQueries({ predicate: queryKeys.cart.predicate })
+
+ // Snapshot the previous value
+ let previousCart = getCurrentCart(queryClient, fields)
+ let didCartExist = true
+
+ if (!previousCart && variables.region) {
+ previousCart = createOptimisticCart(variables.region)
+ didCartExist = false
+ }
+
+ // If we have a cart and product/variant data, we can add optimistically
+ if (previousCart && variables.product !== undefined && variables.variant !== undefined) {
+ const optimisticItem = createOptimisticCartItem(
+ variables.variant,
+ variables.product,
+ variables.quantity
+ )
+
+ addItemOptimistically(queryClient, optimisticItem, previousCart, fields)
+ }
+
+ // Return a context object with the snapshotted value
+ return { previousCart: didCartExist ? previousCart : undefined }
+ },
+ onError: (err, variables, context) => {
+ // If the mutation fails, use the context returned from onMutate to roll back
+ if (context?.previousCart) {
+ rollbackOptimisticCart(queryClient, context.previousCart, fields)
+ }
+ },
+ onSettled: (data) => {
+ // Always refetch after error or success to ensure we have the latest data
+ queryClient.invalidateQueries({ predicate:
+ (query) => queryKeys.cart.predicate(query, fields && data ? [fields] : undefined)
+ })
+ if (data) {
+ queryClient.setQueryData(queryKeys.cart.current(fields), data)
+ }
+ },
+ })
+}
+
+/**
+ * React hook to update line item quantities with optimistic updates.
+ * Provides immediate UI feedback while the request is in progress.
+ *
+ * @param fields - Optional fields to include in the cart response
+ * @returns Tanstack Query mutation object with mutate function and state
+ *
+ * @example
+ * ```typescript
+ * // Basic usage
+ * const updateLineItemMutation = useUpdateLineItem();
+ *
+ * // Usage in component
+ * function QuantitySelector({ lineItem }) {
+ * const updateLineItemMutation = useUpdateLineItem();
+ *
+ * const handleQuantityChange = (newQuantity: number) => {
+ * updateLineItemMutation.mutate({
+ * line_id: lineItem.id,
+ * quantity: newQuantity
+ * }, {
+ * onSuccess: (cart) => {
+ * console.log('Quantity updated:', cart);
+ * },
+ * onError: (error) => {
+ * console.error('Failed to update quantity:', error);
+ * }
+ * });
+ * };
+ *
+ * return (
+ *
+ * );
+ * }
+ * ```
+ */
+export const useUpdateLineItem = ({ fields }: { fields?: string } = {}) => {
+ const queryClient = useQueryClient()
+
+ return useMutation({
+ mutationFn: (variables: {
+ line_id: string;
+ quantity: number;
+ }) => updateLineItem({ ...variables, fields }),
+ onMutate: async (variables) => {
+ // Cancel any outgoing refetches
+ await queryClient.cancelQueries({
+ predicate: (query) => queryKeys.cart.predicate(query, fields ? [fields] : undefined)
+ })
+
+ // Snapshot the previous value
+ const previousCart = getCurrentCart(queryClient, fields)
+
+ // Update optimistically
+ if (previousCart) {
+ updateLineItemOptimistically(queryClient, variables.line_id, variables.quantity, fields)
+ }
+
+ // Return a context object with the snapshotted value
+ return { previousCart }
+ },
+ onError: (err, variables, context) => {
+ // If the mutation fails, use the context returned from onMutate to roll back
+ if (context?.previousCart) {
+ rollbackOptimisticCart(queryClient, context.previousCart, fields)
+ }
+ },
+ onSettled: (data) => {
+ // Always refetch after error or success to ensure we have the latest data
+ queryClient.invalidateQueries({ predicate:
+ (query) => queryKeys.cart.predicate(query, fields && data ? [fields] : undefined)
+ })
+ if (data) {
+ queryClient.setQueryData(queryKeys.cart.current(fields), data)
+ }
+ },
+ })
+}
+
+/**
+ * React hook to delete line items with optimistic updates.
+ * Provides immediate UI feedback while the request is in progress.
+ *
+ * @param fields - Optional fields to include in the cart response
+ * @returns Tanstack Query mutation object with mutate function and state
+ *
+ * @example
+ * ```typescript
+ * // Basic usage
+ * const deleteLineItemMutation = useDeleteLineItem();
+ *
+ * // Usage in component
+ * function CartItem({ lineItem }) {
+ * const deleteLineItemMutation = useDeleteLineItem();
+ *
+ * const handleRemoveItem = () => {
+ * deleteLineItemMutation.mutate({
+ * line_id: lineItem.id
+ * }, {
+ * onSuccess: (cart) => {
+ * console.log('Item removed:', cart);
+ * // Show success message
+ * },
+ * onError: (error) => {
+ * console.error('Failed to remove item:', error);
+ * // Show error message
+ * }
+ * });
+ * };
+ *
+ * return (
+ *
+ * {lineItem.title}
+ *
+ *
+ * );
+ * }
+ * ```
+ */
+export const useDeleteLineItem = ({ fields }: { fields?: string } = {}) => {
+ const queryClient = useQueryClient()
+
+ return useMutation({
+ mutationFn: (variables: {
+ line_id: string;
+ }) => deleteLineItem({ ...variables, fields }),
+ onMutate: async (variables) => {
+ // Cancel any outgoing refetches
+ await queryClient.cancelQueries({ predicate:
+ (query) => queryKeys.cart.predicate(query, fields ? [fields] : undefined)
+ })
+
+ // Snapshot the previous value
+ const previousCart = getCurrentCart(queryClient, fields)
+
+ // Remove optimistically
+ if (previousCart) {
+ removeLineItemOptimistically(queryClient, variables.line_id, fields)
+ }
+
+ // Return a context object with the snapshotted value
+ return { previousCart }
+ },
+ onError: (err, variables, context) => {
+ // If the mutation fails, use the context returned from onMutate to roll back
+ if (context?.previousCart) {
+ rollbackOptimisticCart(queryClient, context.previousCart, fields)
+ }
+ },
+ onSettled: (data) => {
+ // Always refetch after error or success to ensure we have the latest data
+ queryClient.invalidateQueries({ predicate:
+ (query) => queryKeys.cart.predicate(query, fields && data ? [fields] : undefined)
+ })
+ if (data) {
+ queryClient.setQueryData(queryKeys.cart.current(fields), data)
+ }
+ },
+ })
+}
+
+/**
+ * React hook to apply promotion codes to the cart.
+ * Automatically invalidates cart cache on success.
+ *
+ * @returns Tanstack Query mutation object with mutate function and state
+ *
+ * @example
+ * ```typescript
+ * // Basic usage
+ * const applyPromoCodeMutation = useApplyPromoCode();
+ *
+ * // Usage in component
+ * function PromoCodeForm() {
+ * const [code, setCode] = useState('');
+ * const applyPromoCodeMutation = useApplyPromoCode();
+ *
+ * const handleApplyCode = (e: FormEvent) => {
+ * e.preventDefault();
+ *
+ * applyPromoCodeMutation.mutate(
+ * { code },
+ * {
+ * onSuccess: (cart) => {
+ * console.log('Promo code applied:', cart);
+ * setCode('');
+ * // Show success message
+ * },
+ * onError: (error) => {
+ * console.error('Failed to apply promo code:', error);
+ * // Show error message
+ * }
+ * }
+ * );
+ * };
+ *
+ * return (
+ *
+ * );
+ * }
+ * ```
+ */
+export const useApplyPromoCode = () => {
+ const queryClient = useQueryClient()
+
+ return useMutation({
+ mutationFn: applyPromoCode,
+ onSuccess: () => {
+ // Update the cache with the fresh data from the server
+ queryClient.invalidateQueries({ predicate: queryKeys.cart.predicate })
+ },
+ })
+}
+
+/**
+ * React hook to remove promotion codes from the cart.
+ * Automatically invalidates cart cache on success.
+ *
+ * @returns Tanstack Query mutation object with mutate function and state
+ *
+ * @example
+ * ```typescript
+ * // Basic usage
+ * const removePromoCodeMutation = useRemovePromoCode();
+ *
+ * // Usage in component
+ * function AppliedPromoCode({ promoCode }) {
+ * const removePromoCodeMutation = useRemovePromoCode();
+ *
+ * const handleRemoveCode = () => {
+ * removePromoCodeMutation.mutate(
+ * { code: promoCode.code },
+ * {
+ * onSuccess: (cart) => {
+ * console.log('Promo code removed:', cart);
+ * // Show success message
+ * },
+ * onError: (error) => {
+ * console.error('Failed to remove promo code:', error);
+ * // Show error message
+ * }
+ * }
+ * );
+ * };
+ *
+ * return (
+ *
+ * {promoCode.code} - {promoCode.amount} off
+ *
+ *
+ * );
+ * }
+ * ```
+ */
+export const useRemovePromoCode = () => {
+ const queryClient = useQueryClient()
+
+ return useMutation({
+ mutationFn: removePromoCode,
+ onSuccess: () => {
+ // Update the cache with the fresh data from the server
+ queryClient.invalidateQueries({ predicate: queryKeys.cart.predicate })
+ },
+ })
+}
+```
+
+
+
+
+With this setup, your cart mutations will provide immediate UI feedback with optimistic updates, while ensuring that the cart data is always fresh and consistent with the backend.
diff --git a/www/apps/resources/generated/edit-dates.mjs b/www/apps/resources/generated/edit-dates.mjs
index 7761b6e8f6..43c298a72b 100644
--- a/www/apps/resources/generated/edit-dates.mjs
+++ b/www/apps/resources/generated/edit-dates.mjs
@@ -6610,5 +6610,6 @@ export const generatedEditDates = {
"references/core_flows/Locking/core_flows.Locking.Steps_Locking/page.mdx": "2025-09-15T09:52:14.217Z",
"app/nextjs-starter/guides/storefront-returns/page.mdx": "2025-09-22T06:02:00.580Z",
"references/js_sdk/admin/Admin/properties/js_sdk.admin.Admin.views/page.mdx": "2025-09-18T17:04:59.240Z",
- "app/how-to-tutorials/tutorials/agentic-commerce/page.mdx": "2025-10-02T07:14:50.956Z"
+ "app/how-to-tutorials/tutorials/agentic-commerce/page.mdx": "2025-10-02T07:14:50.956Z",
+ "app/storefront-development/production-optimizations/page.mdx": "2025-10-03T13:28:37.909Z"
}
\ No newline at end of file
diff --git a/www/apps/resources/generated/files-map.mjs b/www/apps/resources/generated/files-map.mjs
index 2a1a01f8ba..b8af25e5d6 100644
--- a/www/apps/resources/generated/files-map.mjs
+++ b/www/apps/resources/generated/files-map.mjs
@@ -1279,6 +1279,10 @@ export const filesMap = [
"filePath": "/www/apps/resources/app/storefront-development/page.mdx",
"pathname": "/storefront-development"
},
+ {
+ "filePath": "/www/apps/resources/app/storefront-development/production-optimizations/page.mdx",
+ "pathname": "/storefront-development/production-optimizations"
+ },
{
"filePath": "/www/apps/resources/app/storefront-development/products/categories/list/page.mdx",
"pathname": "/storefront-development/products/categories/list"
diff --git a/www/apps/resources/generated/generated-storefront-development-sidebar.mjs b/www/apps/resources/generated/generated-storefront-development-sidebar.mjs
index ba6872eeb9..52158e865a 100644
--- a/www/apps/resources/generated/generated-storefront-development-sidebar.mjs
+++ b/www/apps/resources/generated/generated-storefront-development-sidebar.mjs
@@ -34,6 +34,14 @@ const generatedgeneratedStorefrontDevelopmentSidebarSidebar = {
"path": "/storefront-development/publishable-api-keys",
"title": "Publishable API Key",
"children": []
+ },
+ {
+ "loaded": true,
+ "isPathHref": true,
+ "type": "link",
+ "path": "/storefront-development/production-optimizations",
+ "title": "Production Optimizations",
+ "children": []
}
]
},
diff --git a/www/apps/resources/sidebars/storefront.mjs b/www/apps/resources/sidebars/storefront.mjs
index 71375471e6..285aa667de 100644
--- a/www/apps/resources/sidebars/storefront.mjs
+++ b/www/apps/resources/sidebars/storefront.mjs
@@ -22,6 +22,11 @@ export const storefrontDevelopmentSidebar = [
path: "/storefront-development/publishable-api-keys",
title: "Publishable API Key",
},
+ {
+ type: "link",
+ path: "/storefront-development/production-optimizations",
+ title: "Production Optimizations",
+ },
],
},
{
diff --git a/www/packages/tags/src/tags/storefront.ts b/www/packages/tags/src/tags/storefront.ts
index cf995442e9..0f0e0d57f3 100644
--- a/www/packages/tags/src/tags/storefront.ts
+++ b/www/packages/tags/src/tags/storefront.ts
@@ -103,6 +103,10 @@ export const storefront = [
"title": "Implement Express Checkout with Medusa",
"path": "https://docs.medusajs.com/resources/storefront-development/guides/express-checkout"
},
+ {
+ "title": "Storefront Production Optimization Tips",
+ "path": "https://docs.medusajs.com/resources/storefront-development/production-optimizations"
+ },
{
"title": "Show Product Categories in Storefront",
"path": "https://docs.medusajs.com/resources/storefront-development/products/categories/list"
diff --git a/www/vale/styles/docs/Tooling.yml b/www/vale/styles/docs/Tooling.yml
index bebbb9b56e..f4958af9ee 100644
--- a/www/vale/styles/docs/Tooling.yml
+++ b/www/vale/styles/docs/Tooling.yml
@@ -36,4 +36,5 @@ exceptions:
- 'Frontend framework'
- 'frontend''s framework'
- 'Frontend''s Framework'
- - 'Frontend''s framework'
\ No newline at end of file
+ - 'Frontend''s framework'
+ - 'your framework'
\ No newline at end of file