Skip to main content

Render a cart from client side

Note

This guide might not be compatible with features introduced in Hydrogen version 2025-05 and above. Check the latest documentation if you encounter any issues.

Returning cart data, along with other data that builds a page, from a Remix loader server-renders the cart. However, if you want to turn on cache control for a page, then the cart shouldn't be server-rendered because carts are user-specific and should never be shared across different user sessions.

The cache control header structure is public, max-age=1, stale-while-revalidate=9. There are many other cache-control configurations available, but this guide focuses on the private and public modes:

  • private: Only the client browser is allowed to cache the request. No servers are allowed to cache the content. private mode is helpful for user account pages, which are relatively static in nature but shouldn't have public servers caching the request due to personalized or sensitive content.

  • public: Public servers like Oxygen, or any other CDNs, are allowed to cache the request. The same content can be reused with multiple users requesting the same page. The content shouldn't contain personalized or sensitive content.

    To set public cache control on your loader data, you'll need to convert your cart to render from the client side.

Anchor to Benefits of turning on cache controlBenefits of turning on cache control

  • Static pages like blogs or articles can be cached longer
  • You can expect cached pages to result in smaller response times compared to non-cached pages if they're served from Oxygen

Anchor to Trade offs of turning on cache controlTrade offs of turning on cache control

  • Expect delayed UI updates for personalized content. For example, if you have a cart icon that displays the number of items in cart, then expect updates to this value to be delayed during a full page load. The delay results from loading the required JavaScript bundles and making a request to get the cart data.
  • UI elements that rely on the client-side cart data fetch, such as the checkout button, aren't functional until the page finishes loading, JavaScript executes, a request to the server returns with cart data, and the client re-renders.
  • Performance metric time to interactive (TTI) will be longer due to the delayed cart's request.

Anchor to Step 1: Add a loader on your cart routeStep 1: Add a loader on your cart route

Create a dedicated route to get a user's cart data. The route also serves as an API. You can get a user's cart by making a fetcher request to /cart.

File

/app/routes/api.cart.jsx

import {json} from '@shopify/remix-oxygen';
import {useLoaderData} from '@remix-run/react';

export async function loader({context}) {
const {cart} = context;
return json(await cart.get());
}

export default function CartRoute() {
const cart = useLoaderData();

return (
<Cart layout="page" cart={cart} />
);
}
import {json, type LoaderArgs} from '@shopify/remix-oxygen';
import {useLoaderData} from '@remix-run/react';

export async function loader({context}: LoaderArgs) {
const {cart} = context;
return json(await cart.get());
}

export default function CartRoute() {
const cart = useLoaderData<typeof loader>();

return (
<Cart layout="page" cart={cart} />
);
}

Anchor to Step 2: Create a provider and a hook for getting the cart dataStep 2: Create a provider and a hook for getting the cart data

Create the <CartProvider> and useCart hook to enable read access to a user's cart across the app:

File

/app/components/CartProvider.jsx

import {createContext, useContext, useEffect} from "react";
import {Cart} from '@shopify/hydrogen-react/storefront-api-types';
import {useFetcher} from "@remix-run/react";

const CartContext = createContext(undefined);

export function CartProvider({children}) {
const fetcher = useFetcher();

useEffect(() => {
fetcher.load('/api/cart');
}, []);

return <CartContext.Provider value={fetcher.data}>{children}</CartContext.Provider>;
}

export function useCart() {
return useContext(CartContext);
}
import {createContext, useContext, useEffect} from "react";
import {Cart} from '@shopify/hydrogen-react/storefront-api-types';
import {useFetcher} from "@remix-run/react";

const CartContext = createContext<Cart | undefined>(undefined);

export function CartProvider({children}: {children: React.ReactNode}) {
const fetcher = useFetcher();

useEffect(() => {
if (fetcher.data || fetcher.state === 'loading') return;

fetcher.load('/api/cart');
}, [fetcher]);

return <CartContext.Provider value={fetcher.data as Cart}>{children}</CartContext.Provider>;
}

export function useCart() {
return useContext(CartContext);
}

Anchor to Step 3: Include your ,[object Object], in your rootStep 3: Include your CartProvider in your root

Include the CartProvider component in your root layout so that your useCart hook is available in the entire app.

File

/app/root.jsx

import {CartProvider} from './components/CartProvider';

export default function App() {
return (
<html lang={locale.language}>
<head>
<meta charSet="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<Seo />
<Meta />
<Links />
</head>
<body>
<CartProvider>
<Layout
key={`${locale.language}-${locale.country}`}
layout={data.layout}
>
<Outlet />
</Layout>
<ScrollRestoration />
<Scripts />
</CartProvider>
</body>
</html>
);
}
import {CartProvider} from './components/CartProvider';

export default function App() {
return (
<html lang={locale.language}>
<head>
<meta charSet="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<Seo />
<Meta />
<Links />
</head>
<body>
<CartProvider>
<Layout
key={`${locale.language}-${locale.country}`}
layout={data.layout}
>
<Outlet />
</Layout>
<ScrollRestoration />
<Scripts />
</CartProvider>
</body>
</html>
);
}

Anchor to Step 4: Get cart content with your ,[object Object], hookStep 4: Get cart content with your useCart hook

Access a user's cart data to do client-side UI rendering from anywhere in your app by using the useCart hook within the <CartProvider> component.

File

/app/components/Layout.jsx

import {useCart} from './CartProvider';

function CartCount({
isHome,
openCart,
}: {
isHome: boolean;
openCart: () => void;
}) {
const cart = useCart();

return (
<Badge
dark={isHome}
openCart={openCart}
count={cart?.totalQuantity || 0}
/>
);
}
import {useCart} from './CartProvider';

function CartCount({
isHome,
openCart,
}: {
isHome: boolean;
openCart: () => void;
}) {
const cart = useCart();

return (
<Badge
dark={isHome}
openCart={openCart}
count={cart?.totalQuantity || 0}
/>
);
}

If you provide cart data in any Remix loaders, then make sure to remove them.

File

/app/root.jsx

export async function loader({context}) {
const {storefront} = context;

return defer({
selectedLocale: storefront.i18n,
// cart: cart.get(),
});
}
export async function loader({context}: LoaderArgs) {
const {storefront} = context;

return defer({
selectedLocale: storefront.i18n,
// cart: cart.get(),
});
}

Was this page helpful?