Shopify / Shopify/hydrogen

Add an SSR-safe account widget renderer and use it in the React Router template

Open
#4,007 0 comments 0 reactions 1 assignee View on GitHub

@Ji-One1 is already working on this.

Since Sep 14, 2026.

Dominant language
TypeScript
Stars
2.1k
Forks
443
Avg merge
4d 19h
Merged PRs (30d)
24

Description

Parent

  • #4005

Context

The <shopify-account> Storefront Web Component renders its interactive button and account sheet in the browser. Its light-DOM signed-out-avatar content is ordinary HTML, so Hydrogen can server-render that fallback before the component loads.

After the account bundle loads, Shopify immediately attaches a shadow root and then fetches storefront data before rendering its internal button and the signed-out-avatar slot. The light-DOM avatar can briefly disappear during that interval. Hydrogen should reserve stable dimensions on the host so adjacent header controls do not move.

What to build

Add a framework-neutral renderer modelled on the existing Shop Pay core API:

type ShopifyAccountWidgetOptions = {
  storeDomain: string;
  publicAccessToken: string;
  customerAccessToken?: string | null;
  menu?: string;
  signInUrl?: string;
  nonce?: string;
  signedOutAvatarHtml: string;
};

renderShopifyAccountWidget(options): string;

signedOutAvatarHtml is required trusted HTML supplied by the application. Its name and TSDoc must make clear that Hydrogen does not escape it and that user-controlled HTML must not be passed directly.

The renderer should produce the structure required by Shopify:

<shopify-store
  store-domain="..."
  public-access-token="..."
  customer-access-token="..."
>
  <shopify-account
    data-hydrogen-account-widget
    sign-in-url="/account/login"
    menu="..."
  >
    <style nonce="...">/* scoped layout reservation */</style>
    <span slot="signed-out-avatar" aria-hidden="true">
      <!-- signedOutAvatarHtml -->
    </span>
  </shopify-account>
</shopify-store>

The <style> should use low-specificity selectors scoped by Hydrogen-owned data attributes. It must style both the <shopify-account> host and the slotted avatar wrapper. Use var(--shopify-account-avatar-size, 44px) so storefront CSS can override Shopify's documented variable.

Do not use a style attribute. CSP nonces cannot authorise style attributes, while a nonce-bearing <style> works with strict style-src policies.

Use the renderer in the React Router template to replace the placeholder account link. The following issue will replace this raw rendering call with the React binding.

Acceptance criteria

  • Export ShopifyAccountWidgetOptions and renderShopifyAccountWidget() from the core package.
  • signedOutAvatarHtml is required and wrapped with slot="signed-out-avatar" by Hydrogen.
  • The renderer emits the required <shopify-store> and <shopify-account> hierarchy.
  • Configuration values are serialised to their documented elements and HTML attributes.
  • Attribute values are escaped, while signedOutAvatarHtml is deliberately treated as trusted markup.
  • customer-access-token is omitted for null and undefined.
  • sign-in-url defaults to /account/login.
  • The generated <style> supports a CSP nonce.
  • The stylesheet reserves the host and avatar wrapper dimensions using var(--shopify-account-avatar-size, 44px).
  • The size reservation remains active after :defined; it must not depend only on the pre-upgrade state.
  • The stylesheet uses low-specificity scoped selectors and does not require user-owned classes or external CSS.
  • The implementation does not define Shopify's custom element, attach a shadow root or emit Declarative Shadow DOM.
  • The React Router template enables accountWidget in ShopifyScripts and renders the widget in its header.
  • The template uses PUBLIC_STOREFRONT_API_TOKEN; it never serialises PRIVATE_STOREFRONT_API_TOKEN.
  • Missing public configuration retains a server-rendered account affordance instead of initialising a broken widget.
  • Template setup documentation covers the public token and Storefront API permissions required by Shopify's account component.
  • Tests cover SSR markup, trusted avatar placement, attribute escaping, optional attributes, nonce output and reserved dimensions.
  • Add TSDoc and a minor changeset.

Verification

  • With JavaScript disabled, the supplied signed-out avatar is visible and occupies the expected account-control space.
  • With JavaScript enabled, Shopify upgrades the component and projects the same avatar into its internal account button.
  • During the empty-shadow-root interval, the account-control space does not collapse or move adjacent header controls.
  • With a customer access token, Shopify renders its signed-in avatar instead of the signed-out slot.

Out of scope

  • Building Customer Account OAuth, sessions or account pages.
  • Reproducing Shopify's internal account button or sheet.
  • Adding framework bindings.
  • Adding createShopifyAccountWidget(); it would currently be a shallow wrapper around document.createElement().
  • Preventing the brief visual disappearance while Shopify awaits storefront data. The requirement is to prevent layout shift.

Blocked by

  • #4006

References

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.