Affiliate Hydration
Affiliate Hydration lets a single cached storefront page render for every visitor — attributed or anonymous — with affiliate-specific values (name, avatar, link URLs) filled in on the client by the FairShare SDK.
The SDK ships custom elements, data attributes, and inline sentinels that the theme author uses in place of the server-rendered {{ affiliate.name }} / {{ affiliate.avatar }} variables. The SDK swaps them at runtime with the resolved affiliate's values (or a fallback for anonymous visitors), and re-swaps them if the affiliate changes mid-session.
For the storefront-theme migration guide, see Affiliate Hydration Cheatsheet.
What's Included
| Item | Type | Description |
|---|---|---|
<fluid-affiliate-name> | Element | Renders the affiliate's full name |
<fluid-affiliate-email> | Element | Renders the affiliate's email address |
<fluid-affiliate-initials> | Element | Renders the affiliate's initials |
<fluid-affiliate-avatar> | Element | Renders the affiliate's avatar URL as text |
<fluid-affiliate-my-site-url> | Element | Renders the affiliate's MyHub URL as text |
data-fluid-affiliate-src | Attribute | Fills the host element's src with an affiliate field |
data-fluid-affiliate-href | Attribute | Fills the host element's href with an affiliate field |
data-fluid-affiliate-alt | Attribute | Fills the host element's alt with an affiliate field |
data-fluid-affiliate | Attribute | Fills the host element's textContent with an affiliate field |
data-fluid-affiliate-base-url | Attribute | Overrides the origin used to resolve my_site_url |
data-fluid-affiliate-show-if | Attribute | Reveals the element when the named field or affiliate is present |
data-fluid-affiliate-hide-if | Attribute | Hides the element when the named field or affiliate is present |
__fluid_affiliate_username__ | Sentinel | Rep-username placeholder swapped in URLs and JSON literals |
Benefits
- One cached page per storefront — The rendered HTML is affiliate-independent, so a single CDN artifact serves every rep and every anonymous visitor
- Automatic swap — The SDK detects the elements and attributes on load and fills them with the resolved affiliate; no explicit initialize call
- Reactive to rep switches — When the visitor navigates from
/rep-a/to/rep-b/, the SDK re-swaps every managed value - Anonymous fallback — Visitors without a rep prefix see fallback content (default text inside the element,
"home"for URL segments) - Framework-agnostic — Works with plain Liquid themes, Alpine.js components, Turbo/Barba SPAs, and any other client-side layer
Quick Example
<!-- Text field: renders the affiliate's name, or "our team" for anonymous visitors --> <p>Shop with <fluid-affiliate-name>our team</fluid-affiliate-name></p> <!-- Attribute field: fills src + alt with affiliate values; hidden when there's no avatar --> <img data-fluid-affiliate-src="avatar" data-fluid-affiliate-alt="name" data-fluid-affiliate-show-if="avatar" style="display:none" /> <!-- URL sentinel: browser gets a real /rep-a/shop link after the SDK swaps --> <a href="/{{ username }}/shop">Shop this rep</a>
<fluid-affiliate-*> fields
Custom elements that render an affiliate field's value as text content. The SDK creates them in the light DOM (no Shadow DOM), so surrounding theme styles apply normally.
Usage
<p>Hi, <fluid-affiliate-name>friend</fluid-affiliate-name></p> <span class="badge"><fluid-affiliate-initials>FA</fluid-affiliate-initials></span> <a><fluid-affiliate-my-site-url>#</fluid-affiliate-my-site-url></a>
Elements
| Element | Fills with | Fallback (anonymous) |
|---|---|---|
<fluid-affiliate-name> | The affiliate's full name | The content inside the tag |
<fluid-affiliate-email> | The affiliate's email address | The content inside the tag |
<fluid-affiliate-initials> | The affiliate's initials | The content inside the tag |
<fluid-affiliate-avatar> | The avatar URL as text | The content inside the tag |
<fluid-affiliate-my-site-url> | The affiliate's MyHub URL as text | The content inside the tag |
Put a sensible default inside the tag — that's what anonymous visitors see. For attributed visitors, the SDK replaces the tag's textContent with the resolved value.
When NOT to use
For an attribute value (src, href, alt), a custom element can't help — HTML doesn't allow tags inside attribute values. Use a data attribute instead.
Data attributes
Markers on any element that tell the SDK to fill one of its HTML attributes (or its textContent) with an affiliate field. Use these for eagerly-fetched attributes (src, href, alt) where a custom element can't sit inside the attribute value.
Usage
<img data-fluid-affiliate-src="avatar" data-fluid-affiliate-alt="name" /> <a data-fluid-affiliate-href="my_site_url">View store</a> <span data-fluid-affiliate="name">friend</span>
Attributes
| Attribute | Fills | Valid <field> values |
|---|---|---|
data-fluid-affiliate-src="<field>" | src | avatar (any URL field) |
data-fluid-affiliate-href="<field>" | href | my_site_url, avatar (any URL field) |
data-fluid-affiliate-alt="<field>" | alt | name (any text field) |
data-fluid-affiliate="<field>" | textContent | Any field — same as using a custom element |
data-fluid-affiliate-base-url="<url>" | Override for my_site_url origin | An absolute URL (e.g. https://staging.example.com) |
<field> is one of name, email, avatar, initials, my_site_url.
data-fluid-affiliate-base-url
By default, the SDK resolves my_site_url against window.location.origin. Add data-fluid-affiliate-base-url on the same element to override:
<!-- Default: uses the current page's origin --> <a data-fluid-affiliate-href="my_site_url">View store</a> <!-- Explicit origin override --> <a data-fluid-affiliate-href="my_site_url" data-fluid-affiliate-base-url="https://staging.example.com" >View store</a>
Only affects my_site_url; other fields ignore this attribute.
Why not <img src="{{ affiliate.avatar }}">?
The browser fetches src before the SDK runs. If you put a raw affiliate value or sentinel in src, the browser makes a broken request to the placeholder URL before the SDK can swap. Always use data-fluid-affiliate-src for eagerly-fetched attributes.
Lazy attributes (<a href>, <form action>, text nodes) are safe to hold sentinels directly.
Visibility toggles
Reveal or hide an element based on whether the SDK has resolved an affiliate — or a specific affiliate field. Handles anonymous vs. attributed rendering from one HTML source.
Usage
<!-- Show only when a rep is attributed --> <div data-fluid-affiliate-show-if="affiliate" style="display:none"> Hi, <fluid-affiliate-name></fluid-affiliate-name> </div> <!-- Show only when the rep has an avatar --> <img data-fluid-affiliate-src="avatar" data-fluid-affiliate-show-if="avatar" style="display:none" /> <!-- Show only for anonymous visitors --> <div data-fluid-affiliate-hide-if="affiliate"> <a href="/join">Become a rep</a> </div>
Tokens
| Token | Element visible when |
|---|---|
affiliate | Any affiliate resolved (not the anonymous / "home" fallback) |
name, email, avatar, initials, my_site_url | That specific field has a value |
hide-if wins over show-if when both target the same element.
Why the inline style="display:none"
The SDK toggles the element after the page loads. Without pre-hiding, an anonymous visitor sees affiliate content flash before it disappears. The SDK removes display:none when the condition holds; leaves it in place when it doesn't. No flash either way.
For accessibility notes on aria-live and screen reader behaviour, see the themes cheatsheet — Accessibility section.
Sentinels
Inline placeholder strings that the SDK swaps document-wide with the resolved rep username. Use these for URL path segments (href, form action) or JSON literals embedded inside <script> tags where a custom element or data attribute can't fit.
Usage
<!-- Liquid source — the raw {{ username }} compiles to a sentinel under hydration --> <a href="/{{ username }}/shop">Shop</a> <form action="/{{ username }}/join">...</form>
Server-rendered HTML:
<a href="/__fluid_affiliate_username__/shop">Shop</a> <form action="/__fluid_affiliate_username__/join">...</form>
At runtime, the SDK replaces every occurrence of __fluid_affiliate_username__ in href, action, text nodes, and JSON literals with the resolved rep username, falling back to "home" for anonymous visitors.
Liquid identifiers that emit sentinels
| Liquid variable | Sentinel emitted | Use for |
|---|---|---|
{{ username }} | __fluid_affiliate_username__ | Path segments in link/form URLs |
{{ share_guid }} | __fluid_affiliate_username__ | Sharing-link surfaces |
{{ affiliate_guid }} | __fluid_affiliate_username__ | Legacy alias for share_guid |
{{ sharing_id }} | __fluid_affiliate_username__ | Sharing link variants |
{{ external_id }} | Real value (not a sentinel) | Backend-only surfaces (webhooks, non-cached) |
{{ company.shop_page_url }} | URL with sentinel baked in | Shop link (no theme edit needed) |
{{ company.join_page_url }} | URL with sentinel baked in | Join link (no theme edit needed) |
Company URL helpers
{{ company.shop_page_url }} and {{ company.join_page_url }} already emit sentinel-bearing URLs under hydration:
<a href="{{ company.shop_page_url }}">Shop</a> <!-- Renders to: <a href="/__fluid_affiliate_username__/shop">Shop</a> -->
The SDK swaps the sentinel client-side. No theme edit needed.
One case that requires care
Raw __fluid_affiliate_username__ sentinels in text nodes can only be swapped once — after the first substitution the original string is gone. For text nodes that need to survive a rep switch mid-session, prefer the <fluid-affiliate-name> custom element over the raw {{ username }} sentinel. The element re-renders on affiliate change; the sentinel doesn't.
For URL paths (/{{ username }}/shop), rep switching triggers a full page navigation anyway, so this rarely comes up in practice.
How the SDK finds them
On page load and on subsequent DOM mutations, the SDK scans the document for the custom elements, data attributes, and sentinels above. Two scan paths:
- Initial scan — Runs once after the SDK boots. Finds every marker in the initial HTML.
- MutationObserver — Watches for
childListchanges ondocument.bodyand re-scans newly-added subtrees. This is what makes SPA navigation (Turbo, Barba) work without explicit hooks — as long as the swap dispatches standard DOM mutations, the SDK picks up the new markers.
If your SPA library performs DOM swaps in a way that skips mutation events, call the SDK's rescan API after each swap:
document.addEventListener('turbo:load', function () { window.FairShareSDK?.rescanForSentinels?.(); });
The optional-chaining call is deliberate — if the SDK isn't loaded yet, the call is a no-op instead of a TypeError.
Fields that stay real (no hydration)
Not every affiliate field goes through hydration. These render with real, per-request values even under hydration:
| Variable | Under hydration |
|---|---|
{{ affiliate.web_rep_store_enabled }} | Real (company-level setting, not per-rep) |
{{ affiliate.logged_in_rep_for_store }} | Real |
{{ affiliate.sign_in_url }} / sign_out_url | Real placeholders ("#") — wire up via SDK auth API, not the Liquid value |
{{ company.* }} (except URL helpers above) | Real (not affiliate-dependent) |
For sign_in_url / sign_out_url, the Liquid renders "#". Wire the click through a JS handler that calls the SDK's auth API — the SDK holds the real URLs even when the Liquid variable doesn't.
Mysite pages don't hydrate
Any template under /my/* (bio pages, favorites, personal links) renders with the real affiliate's values. Hydration is bypassed because mysite content is fundamentally per-rep. In a mysite template you can use {{ affiliate.name }} directly — the SDK isn't relied on for these pages.
Verifying hydration is on
Every response includes diagnostic headers you can use to confirm hydration is running:
curl -I -A "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36" \ "https://<host>/<rep>/products/<slug>" \ | grep -iE "x-hydration|x-credit|x-original-credit|x-cdn-cache|x-gcs-path"
| Header | Meaning |
|---|---|
x-hydration: on | Hydration is active for this request |
x-credit: _default | Cache key is collapsed — every rep for this page shares one artifact |
x-original-credit: <rep> | The rep from the request URL (preserved for analytics) |
x-cdn-cache: HIT / MISS | Cache state at request time |
x-gcs-path: <host>/latest/pages/_default/<page>/... | Path to the cached artifact served for this request |
Use a browser-shaped -A UA — many storefronts return 500 for bare-curl User-Agents.
Related
- Affiliate Hydration Cheatsheet — Full theme-author guide with migration walkthroughs, gotchas, troubleshooting, and pre-flight checklist
- Attribution Overview — How the SDK determines which affiliate to hydrate
- getAttribution — Read the resolved attribution programmatically
- lookupAffiliate — Look up affiliate details by email, username, or ID