Skip to main content
A Content Security Policy (CSP) tells the browser which scripts, styles, frames, and connections your storefront may load. In a Weaverse theme the policy has to allow your own code, Shopify, Weaverse Studio, and every third-party service your sections use.

Who Owns the CSP

Hydrogen 2026.4 ships createContentSecurityPolicy, NonceProvider, and useNonce. Hydrogen 2026.10 removes them, so the theme builds the header and nonce itself. Pilot already does this (Weaverse/pilot#204). It works on Hydrogen 2026.4 and 2026.10, so you can adopt it before you upgrade. Two files are involved: createContentSecurityPolicy keeps the header semantics of Hydrogen 2026.4’s helper:
  • It starts from Hydrogen’s defaults: base-uri 'self', default-src 'self' https://cdn.shopify.com https://shopify.com, frame-ancestors 'none', style-src 'self' 'unsafe-inline' https://cdn.shopify.com, and a connect-src with Shopify’s CDN, Monorail, and your checkout and store domains.
  • Each directive you pass extends the matching default. A 'none' default (such as frame-ancestors) is replaced instead.
  • The nonce goes on script-src when you set it, otherwise on default-src.
  • It returns the header string. You choose the header name.

Wire It Into entry.server.tsx

Generate one nonce per request, build the header, and pass the same nonce to React’s renderer, ServerRouter, and NonceProvider:
app/entry.server.tsx
Hydrogen 2026.10 has no HydrogenRouterContextProvider: the context is React Router’s RouterContextProvider, which server.ts fills with your app context. context.env is typed once env.d.ts declares interface RouterContextProvider extends AppContext {} (see Step 2 of the integration guide). On classic Hydrogen 2026.4, type the parameter as HydrogenRouterContextProvider from @shopify/hydrogen instead.
Unlike Hydrogen’s NonceProvider, this one is a plain React context provider, so it needs value={nonce}.

Wire It Into entry.client.tsx

On the client, read the nonce back from the server-rendered scripts so useNonce() returns the same value during hydration:
app/entry.client.tsx

Use the Nonce in Components

Import useNonce from ~/utils/csp, not from @shopify/hydrogen, and pass it to inline scripts, <ScrollRestoration>, and <Scripts>:
app/root.tsx
Hydrogen’s <Script> component reads Hydrogen’s own nonce context, not this one. Pilot doesn’t use it. If your theme does, render a plain <script nonce={nonce}> instead.

Sources Weaverse Studio Requires

getWeaverseCsp detects design mode from the weaverseHost query parameter that Studio adds to the preview URL. The Weaverse hosts are *.weaverse.io, *.shopify.com, *.myshopify.com, plus the weaverseHost value (or WEAVERSE_HOST from your env).
Once you set script-src, it replaces default-src for scripts. Origins listed only in default-src no longer apply to scripts, so script-src must list every script origin itself, including 'self'. A scriptSrc that only lists https://cdn.shopify.com blocks the Studio bridge, YouTube, and GTM as soon as the header is enforced (Weaverse/pilot#170).

Common Third-Party Sources

Pilot’s getWeaverseCsp covers all of these:
app/weaverse/csp.ts
To allow a new service, add its origin to the directive the browser names in the violation (see below). Add script origins to scriptSrc, not defaultSrc.

Rolling Out Enforcement

Pilot sends Content-Security-Policy-Report-Only: the browser logs violations but blocks nothing. Switch to the enforcing header only after a clean pass.
1

Run report-only

Keep Content-Security-Policy-Report-Only and deploy to a preview environment.
2

Test every surface

With the browser console open, check:
  • Home page
  • A product page, including one with a 3D model
  • A page with a YouTube section, and play the video
  • Cart drawer, variant change, and the mobile menu (these confirm hydration works)
  • GTM loads (window.dataLayer exists and the GTM request succeeds)
  • Studio design mode connects, and edits update the preview
  • Revision preview and section preview in Studio (both load preview.js)
3

Fix each violation

Look for messages like Refused to load the script '<url>' because it violates the following Content Security Policy directive: "script-src ..." (prefixed with [Report Only] in report-only mode). Add the blocked origin to the directive the message names, in app/weaverse/csp.ts, and test again.
4

Enforce

Change the header name in entry.server.tsx:
Repeat the checklist on the enforced build.

Harmless Console Noise

Not every red line is a CSP problem in your policy:
  • CORS errors for googleads.g.doubleclick.net come from inside the YouTube iframe. That frame runs under YouTube’s own policy, not yours, and playback still works.
  • Blocked source-map (.map) fetches under connect-src only happen while DevTools is open. Shoppers never trigger them, so you don’t need to allow them.

Troubleshooting

  • Studio shows a blank preview or never connects: check that frame-ancestors * is present in design mode and that the Weaverse hosts are in script-src and connect-src. See Preview Errors.
  • Studio connects but conditional settings misbehave: 'unsafe-eval' is missing from script-src in design mode.
  • The page renders but nothing is interactive: the nonce on React Router’s scripts doesn’t match the header. Make sure entry.server.tsx passes the same nonce to createContentSecurityPolicy, renderToReadableStream, ServerRouter, and NonceProvider.

Further Reading

Last modified on November 10, 2026