Skip to main content

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:
MethodWhere it runsBest for
useRecommendations() hookBrowser (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.
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>
      ))}
    </>
  );
}
Shopify product ids in Hydrogen are GIDs like gid://shopify/Product/8977367564515. Pass only the numeric part: product.id.split('/').pop().

Page Types

Every request declares which page the visitor is on. Glood returns the sections configured for that page type in the dashboard:
Page TypeUse On
homeHomepage
product_pageProduct detail pages (pass productId)
collectionCollection pages (pass collection id)
cartCart page (pass cartProductIds)
blogBlog and article pages
order_confirmOrder confirmation / thank-you page
checkout, ajax_cart, returns, 404, otherSpecial pages

Hook Result

The hook returns everything needed to render and attribute recommendations:
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:
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:
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>
      ))}
    </>
  );
}
Always wrap the call in a .catch() (or try/catch) inside loaders. A recommendations outage should degrade gracefully, not 500 your page.

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:
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>
    );
  });
}
HelperEvent SentWhen to Call
trackRender(track)glood:section:renderSection is rendered in the DOM
trackView(track)glood:section:viewSection scrolls into the viewport
trackClick(track)glood:section:clickVisitor clicks a recommended product
trackAddToCart(track)glood:section:add_to_cartVisitor adds a recommended product to cart
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.

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:
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:
StatusCause
401Wrong apiKey, or the key doesn’t match myShopifyDomain
400Invalid params (bad pageType, missing pageUrl/locale), or shop plan not set up
CSP error in consolehttps://storefront.glood.ai missing from your connect-src — see 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