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.
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} : {})}
/>
);
}getJoyLoyaltyPageDataloads the page structure, FAQs and translations. It contains no customer data, so the loader result is safe to cache. Customer details come from the rootjoydata and the token route.isLoyaltyPageV2Enabled(data)returnsfalsewhile 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
loyaltyPageUrlat this route. The default is/pages/rewards.
Props
| Prop | Required | What it does |
|---|---|---|
apiUrl | Yes | The Joy headless API URL. |
shopDomain | Yes | Your myshopify domain. |
data | Yes | The result of getJoyLoyaltyPageData. |
initialData | No | The root joy data — shows the customer's details on first paint. |
sectionId | No | Render one section only (see below). |
tokenRoute | No | Defaults to /api/joy-token. |
assetUrl | No | Overrides where Joy code loads from. Leave unset in production. |
cart, cartFetcher, cartRevalidate, cartRoute | No | Same as the widget — lets redeemed coupons apply to the cart. |
onCartUpdated | No | Receives the updated cart so you can sync your cart state. |
onOpenCartDrawer | No | Opens your cart drawer after the cart changes. |
onNavigate | No | Client-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.