Loyalty Page

Render your Joy Loyalty Page V2 natively in a Hydrogen route, with every section you designed in Joy admin.

The Loyalty Page requires Loyalty Page V2. Switch to it in Joy admin before you start. On Weaverse, use the Weaverse blocks instead of a route.

Overview

<JoyLoyaltyPage/> renders the Loyalty Page you designed in Joy admin — hero, ways to earn, rewards, VIP tiers, referrals, FAQ and more — as part of your Hydrogen app. Content and design stay in Joy admin; your route only loads the data and renders the component.

Complete Install manually first.

Create the route

Create a route such as app/routes/($locale).pages.rewards.tsx:

import type {LoaderFunctionArgs} from 'react-router';
import {useLoaderData, useRouteLoaderData, useFetcher, useRevalidator, useNavigate} from 'react-router';
import {getJoyLoyaltyPageData} from '@joy-loyalty/hydrogen/server';
import {JoyLoyaltyPage, isLoyaltyPageV2Enabled} from '@joy-loyalty/hydrogen';
 
export async function loader({context}: LoaderFunctionArgs) {
  const {env, storefront} = context;
  const data = await getJoyLoyaltyPageData({
    storefront,
    apiUrl: env.JOY_API_URL,
    shopDomain: env.JOY_SHOP_DOMAIN,
  });
  return {
    data,
    apiUrl: env.JOY_API_URL,
    shopDomain: env.JOY_SHOP_DOMAIN,
    assetUrl: env.JOY_WIDGET_ASSET_URL ?? null,
  };
}
 
export default function Rewards() {
  const {data, apiUrl, shopDomain, assetUrl} = useLoaderData<typeof loader>();
  const root = useRouteLoaderData('root') as {joy?: unknown} | undefined;
  const fetcher = useFetcher();
  const {revalidate} = useRevalidator();
  const navigate = useNavigate();
 
  if (!isLoyaltyPageV2Enabled(data)) return null;
 
  return (
    <JoyLoyaltyPage
      apiUrl={apiUrl}
      shopDomain={shopDomain}
      data={data}
      initialData={(root?.joy as any) ?? null}
      cartFetcher={fetcher}
      cartRevalidate={revalidate}
      cartRoute="/cart"
      onNavigate={(url) => navigate(url)}
      {...(assetUrl ? {assetUrl} : {})}
    />
  );
}
  • getJoyLoyaltyPageData loads the page structure, FAQs and translations. It contains no customer data, so the loader result is safe to cache. Customer details come from the root joy data and the token route.
  • isLoyaltyPageV2Enabled(data) returns false while your store is on Loyalty Page V1 — the page then renders nothing.
  • Use a locale-aware cartRoute (for example /fr-ca/cart) on multi-market stores.
  • Point the widget's loyaltyPageUrl at this route. The default is /pages/rewards.

Props

PropRequiredWhat it does
apiUrlYesThe Joy headless API URL.
shopDomainYesYour myshopify domain.
dataYesThe result of getJoyLoyaltyPageData.
initialDataNoThe root joy data — shows the customer's details on first paint.
sectionIdNoRender one section only (see below).
tokenRouteNoDefaults to /api/joy-token.
assetUrlNoOverrides where Joy code loads from. Leave unset in production.
cart, cartFetcher, cartRevalidate, cartRouteNoSame as the widget — lets redeemed coupons apply to the cart.
onCartUpdatedNoReceives the updated cart so you can sync your cart state.
onOpenCartDrawerNoOpens your cart drawer after the cart changes.
onNavigateNoClient-side navigation, e.g. useNavigate(). Without it, links do a full page load.

Show a single section

Pass sectionId to render one section — for example, a VIP tiers block on your About page:

<JoyLoyaltyPage sectionId="tiers" apiUrl={apiUrl} shopDomain={shopDomain} data={data} />

Available sections: hero, inlineBanner, howItWorks, earn, redeem, tiers, referral, faq, redeemProgram, myRewards, activity, accountDashboard, referralManagement, walletPass, linkAccounts.

Related