Deploy and verify
Ship your Joy headless integration to Oxygen safely — environment variables, full-page caching, a preview deploy and a guest and member checklist.
Overview
Most "works locally, broken in production" issues come from two things: environment variables missing in one Oxygen environment, and cached pages showing one customer's view to another. This page covers both, then gives you a checklist to run before going live.
Step 1: Add environment variables in Oxygen
In Shopify admin > Hydrogen > your storefront > Storefront settings > Environment variables, add for both Preview and Production (they are separate):
| Variable | Value |
|---|---|
JOY_API_URL | https://api.joy.so/app/api/v1/headless |
JOY_SHOP_DOMAIN | your-store.myshopify.com |
JOY_API_SECRET | Joy admin > Settings > Manage keys > Headless secret (click Generate headless secret) — mark it as secret |
PUBLIC_CHECKOUT_DOMAIN | Your checkout domain (your myshopify domain is fine) |
On @joy-loyalty/hydrogen 0.2.1 or later, don't set JOY_WIDGET_ASSET_URL. On 0.2.0, add JOY_WIDGET_ASSET_URL=https://api.joy.so too — the 0.2.0 default doesn't serve the widget. Never point JOY_API_URL at a local or tunnel address.
Step 2: Check your full-page cache rules
Oxygen can cache whole pages to make your store faster. The catch: Oxygen's cache ignores cookies, so a page cached for a guest can be shown to a logged-in customer — and the widget then says "Sign in" to someone who is already signed in.
Only add Oxygen-Cache-Control to a page when all of these are true:
- It is a
GETrequest with a200response. - The page has no personalized Joy data (
isJoyInitialDataPersonalizedreturnsfalse). - The request has no customer
sessioncookie. - The path (after any locale prefix) doesn't start with
account,api,cart,discount,graphiqlorsubrequest-profiler. - It isn't a Weaverse Studio preview (no
weaverseHost,isDesignMode,__revisionIdand similar parameters).
When you do cache, also set a Vary header. If your app handles React Router data requests separately (for example in handleDataRequest), apply the same rules there.
Pages for logged-in customers must use Cache-Control: private, no-store — the code in Install manually already does this.
Step 3: Deploy to Preview first
- Commit your code — without
.env. - Deploy with
shopify hydrogen deployor your GitHub integration to a Preview environment. - Run the checklist below on the preview URL.
- Promote to Production when every box is ticked.
There is nothing to deploy on the Joy side — changes you make in Joy admin reach your storefront automatically.
Step 4: Verify
As a guest (incognito window):
- The loyalty launcher appears on every page.
- The browser console shows no Content Security Policy errors mentioning
joy.so. -
/api/joy-tokenreturns401 not_logged_inonce — and doesn't repeat. - The Sign in button goes to your login page.
- Your Loyalty Page shows the guest sections.
- Cacheable pages have
Oxygen-Cache-Control; account, cart and API pages don't.
As a logged-in customer:
- Page responses have
Cache-Control: private, no-storeand noOxygen-Cache-Control. -
/api/joy-tokenreturns200with a token. - Points and tier match the customer in Joy admin.
- Redeeming a reward applies the coupon to the cart and opens the cart drawer.
- Switching storefront language (for example
/fr/...) switches Joy texts. - Logging out returns the widget to the guest view.
Something not right? See Troubleshooting.