Skip to content

ShopifyScripts external scripts cause CSP nonce hydration mismatch #3855

Description

@liaoyio

What is the location of your example repository?

No response

Which package or tool is having this issue?

Hydrogen

What version of that package or tool are you using?

0.0.0-preview-8a708a8-20260708155454

What version of React Router 7 are you using?

7.15.1

Steps to Reproduce

Environment:

  • React 19.2.x
  • React Router 7.15.1
  • A request-scoped CSP nonce provided during SSR
  1. Render ShopifyScripts with the nonce:
const nonce = useNonce();

<ShopifyScripts
  nonce={nonce}
  i18n={{country: 'US', language: 'EN'}}
  routes={routes}
/>
  1. On the client, read the server nonce from an existing script's .nonce property and provide it through the same nonce context before hydration.
  2. Perform a full document reload with the browser console open.

Browsers hide a parsed script's nonce content attribute: the DOM exposes nonce="" while the .nonce property retains the real value. Two client-only workarounds are incomplete:

  • Hydrating with undefined still differs from the browser-exposed nonce="".
  • Hydrating with "" makes getShopifyScriptTags() omit nonce attributes because it uses const nonceAttributes = nonce ? {nonce} : undefined.

Expected Behavior

ShopifyScripts should hydrate without reporting an attribute mismatch caused solely by the browser's standard nonce-hiding behavior.

Unrelated hydration diagnostics should remain visible.

Actual Behavior

React reports that attributes of the server-rendered HTML do not match the client properties. The element diff points to the external Shopify standard-actions script generated by ShopifyScripts; its nonce is exposed as nonce="" in the parsed DOM even though the client has the real nonce.

The inline generated scripts do not warn because the React binding already adds suppressHydrationWarning whenever innerHTML is present. The external generated script is the uncovered case.

This produces a hydration warning on every full page load for storefronts using both CSP nonces and the framework-agnostic ShopifyScripts binding, and it obscures unrelated actionable hydration diagnostics.

A minimal fix is to suppress this known comparison when the caller supplied the nonce option, including an empty hydration-frame value:

 return createElement(tagName, {
   key: index,
+  ...(scriptOptions.nonce !== undefined
+    ? {suppressHydrationWarning: true}
+    : {}),
   ...getReactAttributes(attributes),

A narrower variant could apply the condition only to generated script tags. This keeps unrelated hydration warnings visible when no nonce option is present. The same change is needed in development and production bindings.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions