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} : {})}
/>
);
}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.