--- title: Add a content security policy description: Learn how to secure your site with a content security policy. source_url: html: >- https://shopify.dev/docs/storefronts/headless/hydrogen/content-security-policy md: >- https://shopify.dev/docs/storefronts/headless/hydrogen/content-security-policy.md --- ExpandOn this page * [What you'll learn](https://shopify.dev/docs/storefronts/headless/hydrogen/content-security-policy.md#what-youll-learn) * [Requirements](https://shopify.dev/docs/storefronts/headless/hydrogen/content-security-policy.md#requirements) * [Step 1: Set up a content security policy](https://shopify.dev/docs/storefronts/headless/hydrogen/content-security-policy.md#step-1-set-up-a-content-security-policy) * [Step 2: Add a nonce to your scripts](https://shopify.dev/docs/storefronts/headless/hydrogen/content-security-policy.md#step-2-add-a-nonce-to-your-scripts) * [Step 3: Customize the content security policy](https://shopify.dev/docs/storefronts/headless/hydrogen/content-security-policy.md#step-3-customize-the-content-security-policy) # Add a content security policy Note This guide might not be compatible with features introduced in Hydrogen version 2025-05 and above. Check the latest [documentation](https://shopify.dev/docs/api/hydrogen) if you encounter any issues. A [content security policy](https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP) (CSP) adds an important layer of security to your web app by helping to mitigate cross-site scripting and data injection attacks. It enforces what content is loaded in your app. This includes images, CSS, fonts, scripts, network requests, and more. This guide describes how you can set up and customize a CSP for your site. *** ## What you'll learn In this tutorial, you'll learn how to do the following tasks: * Setup a Content Security Policy on an existing Hydrogen app. * Define custom directives within your Content Security Policy. * Secure third party scripts with a Content Security Policy nonce. *** ## Requirements * You've completed the Hydrogen Getting Started with a Hello World example project. *** ## Step 1: Set up a content security policy Hydrogen provides a default content security policy. Add the content security policy by using the [`createContentSecurityPolicy` utility](https://shopify.dev/docs/api/hydrogen/utilities/createcontentsecuritypolicy) within your `entry.server.jsx` file which returns: * `nonce`: Pass this value to React's `renderToReadableStream` or any other custom component that renders a script under the hood. * `NonceProvider`: This makes the nonce available throughout the app, and renders it by wrapping `RemixServer`. * `header`: This is the actual content security policy header value. Add it to your app response headers. Your updated `entry.server.jsx` should look something like the following: ## File ## /app/entry.server.jsx ```jsx import {RemixServer} from '@remix-run/react'; import isbot from 'isbot'; import {renderToReadableStream} from 'react-dom/server'; import {createContentSecurityPolicy} from '@shopify/hydrogen'; export default async function handleRequest( request, responseStatusCode, responseHeaders, remixContext, ) { // Create the Content Security Policy const {nonce, header, NonceProvider} = createContentSecurityPolicy(); const body = await renderToReadableStream( // Wrap the entire app in the nonce provider , { // Pass the nonce to react nonce, signal: request.signal, onError(error) { // eslint-disable-next-line no-console console.error(error); responseStatusCode = 500; }, }, ); if (isbot(request.headers.get('user-agent'))) { await body.allReady; } responseHeaders.set('Content-Type', 'text/html'); // Add the CSP header responseHeaders.set('Content-Security-Policy', header); return new Response(body, { headers: responseHeaders, status: responseStatusCode, }); } ``` ```jsx import type {EntryContext} from '@shopify/remix-oxygen'; import {RemixServer} from '@remix-run/react'; import isbot from 'isbot'; import {renderToReadableStream} from 'react-dom/server'; import {createContentSecurityPolicy} from '@shopify/hydrogen'; export default async function handleRequest( request: Request, responseStatusCode: number, responseHeaders: Headers, remixContext: EntryContext, ) { // Create the Content Security Policy const {nonce, header, NonceProvider} = createContentSecurityPolicy(); const body = await renderToReadableStream( // Wrap the entire app in the nonce provider , { // Pass the nonce to react nonce, signal: request.signal, onError(error) { // eslint-disable-next-line no-console console.error(error); responseStatusCode = 500; }, }, ); if (isbot(request.headers.get('user-agent'))) { await body.allReady; } responseHeaders.set('Content-Type', 'text/html'); // Add the CSP header responseHeaders.set('Content-Security-Policy', header); return new Response(body, { headers: responseHeaders, status: responseStatusCode, }); } ``` ##### JavaScript ``` import {RemixServer} from '@remix-run/react'; import isbot from 'isbot'; import {renderToReadableStream} from 'react-dom/server'; import {createContentSecurityPolicy} from '@shopify/hydrogen'; export default async function handleRequest( request, responseStatusCode, responseHeaders, remixContext, ) { // Create the Content Security Policy const {nonce, header, NonceProvider} = createContentSecurityPolicy(); const body = await renderToReadableStream( // Wrap the entire app in the nonce provider , { // Pass the nonce to react nonce, signal: request.signal, onError(error) { // eslint-disable-next-line no-console console.error(error); responseStatusCode = 500; }, }, ); if (isbot(request.headers.get('user-agent'))) { await body.allReady; } responseHeaders.set('Content-Type', 'text/html'); // Add the CSP header responseHeaders.set('Content-Security-Policy', header); return new Response(body, { headers: responseHeaders, status: responseStatusCode, }); } ``` ##### TypeScript ``` import type {EntryContext} from '@shopify/remix-oxygen'; import {RemixServer} from '@remix-run/react'; import isbot from 'isbot'; import {renderToReadableStream} from 'react-dom/server'; import {createContentSecurityPolicy} from '@shopify/hydrogen'; export default async function handleRequest( request: Request, responseStatusCode: number, responseHeaders: Headers, remixContext: EntryContext, ) { // Create the Content Security Policy const {nonce, header, NonceProvider} = createContentSecurityPolicy(); const body = await renderToReadableStream( // Wrap the entire app in the nonce provider , { // Pass the nonce to react nonce, signal: request.signal, onError(error) { // eslint-disable-next-line no-console console.error(error); responseStatusCode = 500; }, }, ); if (isbot(request.headers.get('user-agent'))) { await body.allReady; } responseHeaders.set('Content-Type', 'text/html'); // Add the CSP header responseHeaders.set('Content-Security-Policy', header); return new Response(body, { headers: responseHeaders, status: responseStatusCode, }); } ``` *** ## Step 2: Add a nonce to your scripts If you start your app's server, you'll notice a lot of errors in the console and scripts will fail to load. This is because the `nonce` value also needs to be passed to each script in the app. 1. Update `root.jsx` to use the [`useNonce()` hook](https://shopify.dev/docs/api/hydrogen/hooks/usenonce) and pass the value to the `ScrollRestoration`, `Scripts`, and `LiveRelod` components. Make sure to also update the error boundary: ## File ## /app/root.jsx ```jsx import {useNonce} from '@shopify/hydrogen'; export default function App() { const nonce = useNonce(); const data = useLoaderData(); return ( {/** Pass the nonce to all components that generate a script **/} ); } export function ErrorBoundary() { const error = useRouteError(); const [root] = useMatches(); const nonce = useNonce(); let errorMessage = "Unknown error"; let errorStatus = 500; if (isRouteErrorResponse(error)) { errorMessage = error?.data?.message ?? error.data; errorStatus = error.status; } else if (error instanceof Error) { errorMessage = error.message; } return (

Oops

{errorStatus}

{errorMessage && (
{errorMessage}
)}
{/** Make sure to remember to pass the nonce to components within the ErrorBoundary **/} ); } ``` ```jsx import {useNonce} from '@shopify/hydrogen'; export default function App() { const nonce = useNonce(); const data = useLoaderData(); return ( {/** Pass the nonce to all components that generate a script **/} ); } export function ErrorBoundary() { const error = useRouteError(); const [root] = useMatches(); const nonce = useNonce(); let errorMessage = "Unknown error"; let errorStatus = 500; if (isRouteErrorResponse(error)) { errorMessage = error?.data?.message ?? error.data; errorStatus = error.status; } else if (error instanceof Error) { errorMessage = error.message; } return (

Oops

{errorStatus}

{errorMessage && (
{errorMessage}
)}
{/** Make sure to remember to pass the nonce to components within the ErrorBoundary **/} ); } ``` ##### JavaScript ``` import {useNonce} from '@shopify/hydrogen'; export default function App() { const nonce = useNonce(); const data = useLoaderData(); return ( {/** Pass the nonce to all components that generate a script **/} ); } export function ErrorBoundary() { const error = useRouteError(); const [root] = useMatches(); const nonce = useNonce(); let errorMessage = "Unknown error"; let errorStatus = 500; if (isRouteErrorResponse(error)) { errorMessage = error?.data?.message ?? error.data; errorStatus = error.status; } else if (error instanceof Error) { errorMessage = error.message; } return (

Oops

{errorStatus}

{errorMessage && (
{errorMessage}
)}
{/** Make sure to remember to pass the nonce to components within the ErrorBoundary **/} ); } ``` ##### TypeScript ``` import {useNonce} from '@shopify/hydrogen'; export default function App() { const nonce = useNonce(); const data = useLoaderData(); return ( {/** Pass the nonce to all components that generate a script **/} ); } export function ErrorBoundary() { const error = useRouteError(); const [root] = useMatches(); const nonce = useNonce(); let errorMessage = "Unknown error"; let errorStatus = 500; if (isRouteErrorResponse(error)) { errorMessage = error?.data?.message ?? error.data; errorStatus = error.status; } else if (error instanceof Error) { errorMessage = error.message; } return (

Oops

{errorStatus}

{errorMessage && (
{errorMessage}
)}
{/** Make sure to remember to pass the nonce to components within the ErrorBoundary **/} ); } ``` 1. If you use any third-party scripts, then use the [`Script` component](https://shopify.dev/docs/api/hydrogen/components/script) which automatically attaches the `nonce`: ## File ## /app/entry.server.jsx ```jsx import {useNonce, Script} from '@shopify/hydrogen'; export default function App() { const nonce = useNonce(); const data = useLoaderData(); return ( {/** The Script component automatically adds a nonce **/}