---
title: Render a cart from client side
description: Set up the cart so that it renders from the client side.
source_url:
  html: >-
    https://shopify.dev/docs/storefronts/headless/legacy/hydrogen/cart/render-client-side
  md: >-
    https://shopify.dev/docs/storefronts/headless/legacy/hydrogen/cart/render-client-side.md
api_name: hydrogen
---

Legacy Hydrogen

These pages refer to Hydrogen versions 2026-04 and earlier. For versions 2026-10 and later, refer to the latest [Hydrogen docs](https://shopify.dev/docs/storefronts/hydrogen). If you're not using the latest version of Hydrogen, then we recommend that you migrate your storefront. To learn more, refer to [Migrate to the latest version of Hydrogen](https://shopify.dev/docs/storefronts/hydrogen/migrate).

# 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 [2026-04 reference](https://shopify.dev/docs/api/hydrogen/2026-04) 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](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control) 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.

### Benefits 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

### Trade 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)](https://developer.chrome.com/en/docs/lighthouse/performance/interactive/) will be longer due to the delayed cart's request.

***

## Step 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 `/api/cart`.

## File

## /app/routes/api.cart.jsx

##### JavaScript

```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} />
  );
}
```

##### TypeScript

```jsx
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} />
  );
}
```

***

## Step 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

##### JavaScript

```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);
}
```

##### TypeScript

```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<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);
}
```

***

## Step 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

##### JavaScript

```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>
  );
}
```

##### TypeScript

```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>
  );
}
```

***

## Step 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

##### JavaScript

```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}
    />
  );
}
```

##### TypeScript

```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}
    />
  );
}
```

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

## File

## /app/root.jsx

##### JavaScript

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

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

##### TypeScript

```jsx
export async function loader({context}: LoaderArgs) {
  const {storefront} = context;

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

***
