Parent
Context
Shopify's <shopify-account> web component shows the customer's avatar and account sheet. The code for it lives in a separate script on Shopify's CDN. Right now Hydrogen users have to add that <script> tag by hand.
This task adds an accountWidget switch to ShopifyScripts so Hydrogen adds the script for you. Loading the script only teaches the browser what <shopify-account> is. It does not put an account widget on the page - that's still up to the developer.
How ShopifyScripts works (read this first)
- Core -
packages/hydrogen/src/core/shopify-scripts/index.ts has one function, getShopifyScriptTags(), that decides which <script> tags a storefront needs. It returns a plain list: URL + attributes for each script. This is the only place that decides what loads.
- React (
packages/hydrogen/src/react/shopify-scripts.tsx) and Vue (packages/hydrogen/src/vue/shopify-scripts.ts) just take that list and print the tags. They make no decisions.
So: the real change is in core. React needs no code change (it passes all options straight through). Vue needs one line because Vue requires every prop to be declared - the build will fail until you add it, which is intentional.
Use the existing inbox option as your template at every step.
What to build
Add an optional accountWidget flag. When true, output:
<script
id="shopify-account-widget"
type="module"
async
crossorigin="anonymous"
src="https://cdn.shopify.com/storefront/web-components/account.js"
></script>
Decisions already made:
async: yes (same as the Inbox script).
id: shopify-account-widget (every other script has an id; keep the convention).
- Position: add it last in the list. Order isn't important for this script.
Steps
- Constant - in
core/shopify-scripts/constants.ts, add SHOPIFY_ACCOUNT_WIDGET_SCRIPT next to SHOPIFY_INBOX_SCRIPT. Export it from core/shopify-scripts/index.ts so tests import it instead of copy-pasting the URL.
- Option type - in
core/shopify-scripts/types.ts, add accountWidget?: boolean next to inbox. Write a short TSDoc comment: loads the <shopify-account> element definition, does not render it.
- Core logic - in
getShopifyScriptTags(), default it to false, and add an if (accountWidget) block after the Inbox one that pushes the descriptor above. Spread the existing nonceAttributes so CSP nonces work like every other script.
- Vue - in
vue/shopify-scripts.ts, declare accountWidget: { type: Boolean, default: undefined } in the props list (copy the inbox line).
- React - no change needed. Do not add
shopify-account to the JSX element types; rendering the element is out of scope.
- Tests - see below.
- Docs - mention the opt-in in
packages/hydrogen/skills/hydrogen-setup/steps/7-shopify-runtime-scripts.md, written for someone using Hydrogen (not someone building it).
- Changeset - add
.changeset/<name>.md with '@shopify/hydrogen': minor (see accept-variant-id.md for the shape).
Tests
Test the decision in core and the printing in React/Vue. Don't repeat the same checks in three places.
core/shopify-scripts.test.ts: copy the Inbox tests. accountWidget: true -> the exact descriptor is in the list, including the nonce. Default -> it is not in the list. Add accountWidget: true to the "preserves an explicitly empty nonce" test and bump its expected length.
react/shopify-scripts.test.tsx and vue/shopify-scripts.test.ts: add accountWidget: true to the existing big SSR render test and assert the HTML contains the account script tag. That's enough.
- Use
assert from core/test-utils.ts. Never use !.
Try it by hand in the React Router template
-
Open templates/react-router/app/root.tsx. Find the <ShopifyScripts ... /> tag in the <head> and add accountWidget to it:
<ShopifyScripts
i18n={storefrontConfig.i18n}
shop={shop}
consent={analyticsConsent}
navigate={navigate}
routes={routeTemplates}
accountWidget
/>
-
Put the widget somewhere visible. In the same file, inside <body> just above {children}, paste plain HTML (this is not React-specific - it's just custom HTML tags):
<shopify-store
store-domain="your-store.myshopify.com"
public-access-token="your-public-access-token"
country="US"
language="EN"
>
<shopify-account></shopify-account>
</shopify-store>
TypeScript will complain that it doesn't know these tags. That's expected and fine for a local test - ignore it, or add {/* @ts-expect-error */} above each tag. Don't commit this.
-
Run pnpm --filter @shopify/hydrogen-template-react-router dev, open the page and check:
- View source: the
account.js script tag is in <head>.
- In the browser console,
customElements.get('shopify-account') returns a function (not undefined). This proves the script loaded and defined the element.
- Remove
accountWidget and reload: the tag is gone and customElements.get('shopify-account') is undefined.
-
To see the actual avatar you need a real store with the Customer Account API enabled and a public access token from the Headless channel - mock.shop (the template default) does not support it. Set PUBLIC_STORE_DOMAIN etc. in .env per the template README. The account component guide covers the store-side setup.
Acceptance criteria
Out of scope
- Rendering
<shopify-account> or <shopify-store> for the user.
- Customer Account API or OAuth setup.
- React/Vue wrappers for the account widget.
Blocked by
None.
References
Parent
Context
Shopify's
<shopify-account>web component shows the customer's avatar and account sheet. The code for it lives in a separate script on Shopify's CDN. Right now Hydrogen users have to add that<script>tag by hand.This task adds an
accountWidgetswitch toShopifyScriptsso Hydrogen adds the script for you. Loading the script only teaches the browser what<shopify-account>is. It does not put an account widget on the page - that's still up to the developer.How
ShopifyScriptsworks (read this first)packages/hydrogen/src/core/shopify-scripts/index.tshas one function,getShopifyScriptTags(), that decides which<script>tags a storefront needs. It returns a plain list: URL + attributes for each script. This is the only place that decides what loads.packages/hydrogen/src/react/shopify-scripts.tsx) and Vue (packages/hydrogen/src/vue/shopify-scripts.ts) just take that list and print the tags. They make no decisions.So: the real change is in core. React needs no code change (it passes all options straight through). Vue needs one line because Vue requires every prop to be declared - the build will fail until you add it, which is intentional.
Use the existing
inboxoption as your template at every step.What to build
Add an optional
accountWidgetflag. Whentrue, output:Decisions already made:
async: yes (same as the Inbox script).id:shopify-account-widget(every other script has anid; keep the convention).Steps
core/shopify-scripts/constants.ts, addSHOPIFY_ACCOUNT_WIDGET_SCRIPTnext toSHOPIFY_INBOX_SCRIPT. Export it fromcore/shopify-scripts/index.tsso tests import it instead of copy-pasting the URL.core/shopify-scripts/types.ts, addaccountWidget?: booleannext toinbox. Write a short TSDoc comment: loads the<shopify-account>element definition, does not render it.getShopifyScriptTags(), default it tofalse, and add anif (accountWidget)block after the Inbox one that pushes the descriptor above. Spread the existingnonceAttributesso CSP nonces work like every other script.vue/shopify-scripts.ts, declareaccountWidget: { type: Boolean, default: undefined }in the props list (copy theinboxline).shopify-accountto the JSX element types; rendering the element is out of scope.packages/hydrogen/skills/hydrogen-setup/steps/7-shopify-runtime-scripts.md, written for someone using Hydrogen (not someone building it)..changeset/<name>.mdwith'@shopify/hydrogen': minor(seeaccept-variant-id.mdfor the shape).Tests
Test the decision in core and the printing in React/Vue. Don't repeat the same checks in three places.
core/shopify-scripts.test.ts: copy the Inbox tests.accountWidget: true-> the exact descriptor is in the list, including the nonce. Default -> it is not in the list. AddaccountWidget: trueto the "preserves an explicitly empty nonce" test and bump its expected length.react/shopify-scripts.test.tsxandvue/shopify-scripts.test.ts: addaccountWidget: trueto the existing big SSR render test and assert the HTML contains the account script tag. That's enough.assertfromcore/test-utils.ts. Never use!.Try it by hand in the React Router template
Open
templates/react-router/app/root.tsx. Find the<ShopifyScripts ... />tag in the<head>and addaccountWidgetto it:Put the widget somewhere visible. In the same file, inside
<body>just above{children}, paste plain HTML (this is not React-specific - it's just custom HTML tags):TypeScript will complain that it doesn't know these tags. That's expected and fine for a local test - ignore it, or add
{/* @ts-expect-error */}above each tag. Don't commit this.Run
pnpm --filter @shopify/hydrogen-template-react-router dev, open the page and check:account.jsscript tag is in<head>.customElements.get('shopify-account')returns a function (notundefined). This proves the script loaded and defined the element.accountWidgetand reload: the tag is gone andcustomElements.get('shopify-account')isundefined.To see the actual avatar you need a real store with the Customer Account API enabled and a public access token from the Headless channel -
mock.shop(the template default) does not support it. SetPUBLIC_STORE_DOMAINetc. in.envper the template README. The account component guide covers the store-side setup.Acceptance criteria
ShopifyScriptTagsOptionsincludesaccountWidget?: booleanwith TSDoc saying it loads the element but does not render it.ShopifyScriptsaccept the sameaccountWidgetprop.getShopifyScriptTags()andrenderShopifyScriptTags()include the account script (withid,type="module",async,crossorigin="anonymous", nonce) whenaccountWidgetistrue.false.hydrogen-setupskill mentions the opt-in.Out of scope
<shopify-account>or<shopify-store>for the user.Blocked by
None.
References
<shopify-store>referencepackages/hydrogen/src/core/shopify-scripts/packages/hydrogen/src/react/shopify-scripts.tsxpackages/hydrogen/src/vue/shopify-scripts.tstemplates/react-router/app/root.tsx