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

# Recommendations Example

> Complete working example: product page and homepage recommendations with attribution tracking

# Recommendations Example

A complete, tested integration showing recommendations on a product page and the homepage, with full attribution tracking. Copy the pieces you need.

## 1. Client Setup

Create the client once and register the recommendations app:

```tsx theme={null}
// app/lib/glood.ts
import { createGlood, recommendations } from '@glood/hydrogen';

export const glood = createGlood({
  apiKey: 'gl_sf_your_storefront_token',
  myShopifyDomain: 'your-store.myshopify.com',
  debug: import.meta.env.DEV, // verbose [Glood Debug] console logs in dev
}).use(recommendations());
```

Wrap your app in `root.tsx` (inside Shopify's `Analytics.Provider`):

```tsx theme={null}
// app/root.tsx
import { Analytics } from '@shopify/hydrogen';
import { GloodProvider } from '@glood/hydrogen';
import { glood } from '~/lib/glood';

export default function App() {
  const data = useRouteLoaderData('root');

  return (
    <Analytics.Provider cart={data.cart} shop={data.shop} consent={data.consent}>
      <GloodProvider client={glood} loaderData={data}>
        <PageLayout {...data}>
          <Outlet />
        </PageLayout>
      </GloodProvider>
    </Analytics.Provider>
  );
}
```

## 2. Reusable Recommendations Component

One component that fetches, renders, and attributes recommendations for any page type:

```tsx theme={null}
// app/components/GloodRecommendations.tsx
import { useEffect } from 'react';
import { useRecommendations } from '@glood/hydrogen';

export function GloodRecommendations({
  pageType,
  productId,
  cartProductIds,
}: {
  pageType: string;
  productId?: string;
  cartProductIds?: string[];
}) {
  const {
    sections,
    data,
    loading,
    error,
    trackRender,
    trackClick,
    trackAddToCart,
  } = useRecommendations({ pageType, productId, cartProductIds });

  // Attribute each section render once the response arrives
  useEffect(() => {
    if (!data) return;
    data.sections.forEach((section) => {
      trackRender({
        section: String(section.id),
        sectionServeId: section.section_serve_id,
        requestId: data.request_id,
        page: pageType,
        parent: productId ? { productId } : undefined,
      });
    });
  }, [data, pageType, productId, trackRender]);

  if (loading) return <p>Loading recommendations…</p>;
  if (error || sections.length === 0) return null;

  return (
    <div className="glood-recommendations">
      {sections.map((section) => (
        <section key={section.section_serve_id}>
          <h2>{section.title}</h2>
          <div className="product-grid">
            {section.products.map((product) => {
              const track = {
                section: String(section.id),
                sectionServeId: section.section_serve_id,
                requestId: data?.request_id,
                page: pageType,
                parent: productId ? { productId } : undefined,
                products: [
                  {
                    productId: String(product.product_id),
                    variantId: product.variants[0]
                      ? String(product.variants[0].variant_id)
                      : undefined,
                    quantity: 1,
                  },
                ],
              };

              return (
                <div key={product.product_id} className="product-card">
                  <a
                    href={`/products/${product.handle}`}
                    onClick={() => trackClick(track)}
                  >
                    <img src={product.image?.url} alt={product.title} loading="lazy" />
                    <h3>{product.title}</h3>
                    <p>
                      {product.price}
                      {product.compare_at_price &&
                        product.compare_at_price > product.price && (
                          <s>{product.compare_at_price}</s>
                        )}
                    </p>
                  </a>
                  <button
                    onClick={() => {
                      // ...add to cart via your cart handler, then attribute:
                      trackAddToCart(track);
                    }}
                  >
                    Add to cart
                  </button>
                </div>
              );
            })}
          </div>
        </section>
      ))}
    </div>
  );
}
```

## 3. Use It on Any Page

**Product page** — pass the numeric product id (strip the GID prefix):

```tsx theme={null}
// app/routes/products.$handle.tsx
import { GloodRecommendations } from '~/components/GloodRecommendations';

export default function Product() {
  const { product } = useLoaderData();

  return (
    <div className="product">
      {/* ...product details... */}
      <GloodRecommendations
        pageType="product_page"
        productId={product.id.split('/').pop()}
      />
    </div>
  );
}
```

**Homepage:**

```tsx theme={null}
// app/routes/_index.tsx
<GloodRecommendations pageType="home" />
```

**Cart page** — pass what's in the cart so Glood can exclude/complement it:

```tsx theme={null}
<GloodRecommendations
  pageType="cart"
  cartProductIds={cart.lines.nodes.map((line) =>
    line.merchandise.product.id.split('/').pop(),
  )}
/>
```

## 4. Server-Side Rendering Variant

To include recommendations in the initial HTML, fetch in the route loader instead:

```tsx theme={null}
// app/routes/products.$handle.tsx
import { fetchRecommendationSections } from '@glood/hydrogen';

export async function loader({ request, params, context }) {
  const [product, recommendations] = await Promise.all([
    loadProduct(context, params),
    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), // degrade gracefully
  ]);

  return { product, recommendations };
}

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

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

<Note>
  With SSR you still get attribution: retrieve the app client-side with `client.getApp('recommendations')` (or the `useRecommendations` track helpers) and call `trackRender`/`trackClick` with the `section_serve_id` values from your loader data.
</Note>

## 5. Don't Forget the CSP

Recommendation fetches and pixel events are blocked by Hydrogen's default Content Security Policy. Add Glood's domains in `entry.server`:

```tsx theme={null}
const { nonce, header, NonceProvider } = createContentSecurityPolicy({
  shop: {
    checkoutDomain: context.env.PUBLIC_CHECKOUT_DOMAIN,
    storeDomain: context.env.PUBLIC_STORE_DOMAIN,
  },
  connectSrc: [
    "'self'",
    'https://storefront.glood.ai', // recommendations API
    'https://events.glood.ai',     // pixel events
  ],
});
```

See [Content Security Policy](/for-developers/glood-hydrogen-sdk/content-security-policy) for details.

## What You'll See in the Console (debug: true)

```
[Glood Debug] Registered recommendations app
[Glood Debug] Fetching sections: https://storefront.glood.ai/... {page_type: 'home', ...}
[Glood Debug] Received 5 sections
[Glood Debug] Setting up centralized subscription for page_viewed
[Glood Debug] Received page_viewed event, distributing to interested apps
[Glood Debug] Consent granted for recommendations, processing event
[Glood Debug] Successfully sent event to https://events.glood.ai/api/storefront/event
```

## See Also

* [Loading Recommendations Guide](/for-developers/glood-hydrogen-sdk/recommendations)
* [Recommendations API Reference](/for-developers/glood-hydrogen-sdk/api-reference/recommendations-api)
* [Basic Setup Example](/for-developers/glood-hydrogen-sdk/examples/basic-setup)
