Product Genius Docs

Content-Security-Policy

The Product Genius hosts to allow on script-src and connect-src, and how to thread a nonce through.

<ProductGeniusFeed> injects the Product Genius script and the script opens connections to the Product Genius backend, including a websocket. If your storefront sets a Content-Security-Policy, it has to allow those hosts or nothing loads.

The hosts

script-src  'self' https://app.productgenius.io https://pg.gamalon.com;
connect-src 'self' https://app.productgenius.io wss://app.productgenius.io https://pg.gamalon.com;

Merge these into your existing directives — the 'self' above is a reminder of what a typical policy already has, not a replacement for the rest of your allowlist. Note that connect-src needs both the https: and the wss: form of app.productgenius.io.

These two directives are what the component and the bundle need in order to load and connect. The feed's own markup, styles, and images are produced by the bundle after it loads, so if your policy is also strict about img-src, style-src, or font-src, verify a rendered feed on staging and add whatever your console reports.

Nonce-based policies

If your CSP is purely host-allowlisted, you're done — omit the nonce prop.

If your script-src is nonce-based (commonly with 'strict-dynamic'), an un-nonced script is blocked and the feed script never loads. Thread your per-request nonce through the prop:

<ProductGeniusFeed shop="store.myshopify.com" nonce={nonce} /* … */ />

SSR frameworks generate a nonce per request — Hydrogen's createContentSecurityPolicy() and useNonce(), Next.js proxy/middleware, React Router and TanStack Start entry servers. A purely client-rendered SPA usually has no nonce at all, because its CSP comes from static hosting headers or a <meta http-equiv> tag. The framework guides cover where each one's nonce comes from.

The search bar takes its own nonce

<ProductGeniusSearchBar> renders an inline <style> element, so it needs a nonce whenever your style-src doesn't allow inline styles without one. Browsers ignore 'unsafe-inline' when the directive also carries a nonce or a hash. It's usually the same per-request nonce value you pass to the feed, just satisfying a different directive.

Hydrogen

Hydrogen builds the policy for you in app/entry.server.tsx; add the hosts to the existing call:

app/entry.server.tsx
const {nonce, header, NonceProvider} = createContentSecurityPolicy({
  shop: {
    checkoutDomain: context.env.PUBLIC_CHECKOUT_DOMAIN,
    storeDomain: context.env.PUBLIC_STORE_DOMAIN,
  },
  scriptSrc: [
    "'self'",
    'https://cdn.shopify.com',
    'https://app.productgenius.io',
    'https://pg.gamalon.com',
  ],
  connectSrc: [
    "'self'",
    'https://app.productgenius.io',
    'wss://app.productgenius.io',
    'https://pg.gamalon.com',
  ],
});

Hydrogen's default policy is nonce-based, so also pass useNonce() to the feed's nonce prop.

When CSP is the problem

A blocked script is the most common "the feed isn't showing" cause on the storefront side, and it is easy to confirm:

  • No tag.js request at all in the Network tab — either the component isn't rendered on this route, or script-src blocked it. The console names the directive.
  • tag.js returns 200 but window.GAMALON is undefined — the script loaded but was blocked from executing, which on a nonce-based policy usually means the nonce prop wasn't passed.
  • Feed requests failing with a console error naming connect-src — the backend host or the wss: entry is missing.

The full checklist is in the integration guide.

How is this guide?

On this page