> ## Documentation Index
> Fetch the complete documentation index at: https://docs.glood.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Loading Recommendations

> Fetch and display Glood recommendation sections with full product data in your Hydrogen storefront

# Loading Recommendations

The SDK fetches the recommendation sections you configure in the Glood dashboard — complete with full product data (title, handle, prices, images, options, and variants) — so you can render them with your own React components.

There are two ways to load recommendations:

| Method                          | Where it runs                   | Best for                                                         |
| ------------------------------- | ------------------------------- | ---------------------------------------------------------------- |
| `useRecommendations()` hook     | Browser (client-side)           | Most storefronts — simplest integration                          |
| `fetchRecommendationSections()` | Server (Hydrogen route loaders) | SEO-critical pages that need recommendations in the initial HTML |

## Client-Side: useRecommendations Hook

Use the hook inside any component rendered under `<GloodProvider>`. The page URL, locale, and Glood client id are filled in automatically from the browser.

```tsx theme={null}
import { useRecommendations } from '@glood/hydrogen';

function ProductRecommendations({ productId }: { productId: string }) {
  const { sections, loading, error } = useRecommendations({
    pageType: 'product_page',
    productId, // numeric Shopify product id, e.g. '8977367564515'
  });

  if (loading || error) return null;

  return (
    <>
      {sections.map((section) => (
        <section key={section.section_serve_id}>
          <h2>{section.title}</h2>
          <div className="product-grid">
            {section.products.map((product) => (
              <a key={product.product_id} href={`/products/${product.handle}`}>
                <img src={product.image?.url} alt={product.title} />
                <span>{product.title}</span>
                <span>{product.price}</span>
              </a>
            ))}
          </div>
        </section>
      ))}
    </>
  );
}
```

<Note>
  Shopify product ids in Hydrogen are GIDs like `gid://shopify/Product/8977367564515`. Pass only the numeric part: `product.id.split('/').pop()`.
</Note>

### Page Types

Every request declares which page the visitor is on. Glood returns the sections configured for that page type in the dashboard:

| Page Type                                          | Use On                                  |
| -------------------------------------------------- | --------------------------------------- |
| `home`                                             | Homepage                                |
| `product_page`                                     | Product detail pages (pass `productId`) |
| `collection`                                       | Collection pages (pass `collection` id) |
| `cart`                                             | Cart page (pass `cartProductIds`)       |
| `blog`                                             | Blog and article pages                  |
| `order_confirm`                                    | Order confirmation / thank-you page     |
| `checkout`, `ajax_cart`, `returns`, `404`, `other` | Special pages                           |

### Hook Result

The hook returns everything needed to render and attribute recommendations:

```tsx theme={null}
const {
  sections,        // RecommendationSection[] — sections with products
  data,            // Full SectionsResponse (request_id, experience, ...)
  loading,         // true while fetching
  error,           // Error | null
  refetch,         // Re-run the request with the same params
  trackRender,     // Attribution helpers (see below)
  trackView,
  trackClick,
  trackAddToCart,
} = useRecommendations({ pageType: 'product_page', productId });
```

### Deferring the Request

Pass `{ skip: true }` as the second argument to wait until required params are ready:

```tsx theme={null}
const { sections } = useRecommendations(
  { pageType: 'product_page', productId },
  { skip: !productId },
);
```

## Server-Side: fetchRecommendationSections

For SSR, fetch recommendations inside a Hydrogen route loader. Pass `pageUrl` and `locale` explicitly — there is no browser context on the server:

```tsx theme={null}
import { fetchRecommendationSections } from '@glood/hydrogen';
import { useLoaderData } from 'react-router';

export async function loader({ request, params, context }) {
  const recommendations = await fetchRecommendationSections(
    {
      apiKey: context.env.GLOOD_API_KEY,
      myShopifyDomain: 'your-store.myshopify.com',
    },
    {
      pageType: 'product_page',
      pageUrl: request.url,
      locale: 'en-US',
      productId: params.productId,
    },
  ).catch(() => null); // Never let recommendations break the page

  return { recommendations };
}

export default function ProductPage() {
  const { recommendations } = useLoaderData();

  return (
    <>
      {recommendations?.sections.map((section) => (
        <section key={section.section_serve_id}>
          <h2>{section.title}</h2>
          {/* render section.products */}
        </section>
      ))}
    </>
  );
}
```

<Warning>
  Always wrap the call in a `.catch()` (or try/catch) inside loaders. A recommendations outage should degrade gracefully, not 500 your page.
</Warning>

## Attribution Tracking

To attribute recommendation performance (views, clicks, and add-to-carts tied back to revenue in Glood analytics), fire the attribution helpers at the right moments. Each takes a `SectionTrack` object that links the interaction to the exact section render via `sectionServeId`:

```tsx theme={null}
function ProductRecommendations({ productId }) {
  const { sections, data, trackRender, trackClick, trackAddToCart } =
    useRecommendations({ pageType: 'product_page', productId });

  return sections.map((section) => {
    const baseTrack = {
      section: String(section.id),
      sectionServeId: section.section_serve_id, // from the API response
      requestId: data?.request_id,
      page: 'product_page',
      parent: { productId },
    };

    return (
      <section
        key={section.section_serve_id}
        ref={() => trackRender(baseTrack)}
      >
        <h2>{section.title}</h2>
        {section.products.map((product) => (
          <a
            key={product.product_id}
            href={`/products/${product.handle}`}
            onClick={() =>
              trackClick({
                ...baseTrack,
                products: [{ productId: String(product.product_id) }],
              })
            }
          >
            {product.title}
          </a>
        ))}
      </section>
    );
  });
}
```

| Helper                  | Event Sent                  | When to Call                               |
| ----------------------- | --------------------------- | ------------------------------------------ |
| `trackRender(track)`    | `glood:section:render`      | Section is rendered in the DOM             |
| `trackView(track)`      | `glood:section:view`        | Section scrolls into the viewport          |
| `trackClick(track)`     | `glood:section:click`       | Visitor clicks a recommended product       |
| `trackAddToCart(track)` | `glood:section:add_to_cart` | Visitor adds a recommended product to cart |

<Note>
  Standard events (`page_viewed`, `product_added_to_cart`, etc.) are sent automatically by `GloodProvider` — you never emit those manually. Attribution helpers are the only events you call yourself, because only your UI knows when a recommendation is clicked.
</Note>

## Experience Persistence

The sections API assigns each visitor an A/B **experience**. The SDK automatically persists the assigned experience id to `localStorage` (key `gai_s_e`) after every fetch, and every pixel event carries it — so all analytics segment correctly by experience without any work on your side.

## Error Handling

Failed requests throw (or surface via the hook's `error`) a `GloodApiError` carrying the HTTP status and response body:

```tsx theme={null}
import { GloodApiError } from '@glood/hydrogen';

try {
  await fetchRecommendationSections(options, params);
} catch (error) {
  if (error instanceof GloodApiError) {
    console.error(error.status, error.body); // e.g. 401, { error: 'Invalid authentication' }
  }
}
```

Common failures:

| Status               | Cause                                                                                                                                                     |
| -------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `401`                | Wrong `apiKey`, or the key doesn't match `myShopifyDomain`                                                                                                |
| `400`                | Invalid params (bad `pageType`, missing `pageUrl`/`locale`), or shop plan not set up                                                                      |
| CSP error in console | `https://storefront.glood.ai` missing from your `connect-src` — see [Content Security Policy](/for-developers/glood-hydrogen-sdk/content-security-policy) |

Empty `sections: []` with a `200` is not an error — it means no sections are enabled for that page type in the Glood dashboard (or targeting rules excluded them).

## See Also

* [Recommendations API Reference](/for-developers/glood-hydrogen-sdk/api-reference/recommendations-api) - Full signatures, parameters, and return types
* [Recommendations Example](/for-developers/glood-hydrogen-sdk/examples/recommendations) - Complete working page
* [TypeScript Types](/for-developers/glood-hydrogen-sdk/api-reference/types) - `SectionsResponse`, `RecommendationSection`, `SectionProduct`
* [Content Security Policy](/for-developers/glood-hydrogen-sdk/content-security-policy) - Required CSP entries
