Stay safe, no more unsafe-inline
Learn more about unsafe-inline and how to properly setup your CSP to avoid using it.
2024-12-03
5 min read

Theotime Quere
Read more →
Main menu
All articles
Next Article
Get started with CSP
Content Security Policy is an important security feature that helps protect your web application from various attacks, particularly Cross-Site Scripting (XSS). When using Next.js with the App Router, implementing CSP with nonce-based script security requires some setup. Here's a complete guide on how to do it.
Before diving into the implementation, let's understand what CSP and nonce are and why they're important:
Content Security Policy is a security standard that helps prevent various types of attacks, especially XSS attacks, by controlling which resources can be loaded
A nonce is a unique, random number used once that helps validate trusted scripts and styles
Together, they create a robust security system where only scripts with valid nonces are allowed to execute
Important Note
This implementation is specifically for the Pages Router in Next.js. It's important to note that nonce values are only accessible server-side. Therefore, any components or features that require the nonce must be configured and rendered on the server side. Client-side components won't have access to the nonce value.
First, let's create a utility function to generate nonces. Create a new file `utils/nonce.ts`. This function uses Node's crypto module to generate a cryptographically secure random value:
// utils/nonce.ts
// Important: This function must be used server-side only as crypto is a Node.js module
export function generateNonce() {
return Buffer.from(crypto.randomBytes(16)).toString('base64');
}
The function above creates a 16-byte random value and converts it to a base64 string, making it safe for use in HTML attributes and headers.
Create or modify your `middleware.ts` file in the root of your project to add CSP headers. The middleware runs before each request and sets up the security headers:
// middleware.ts
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
import { generateNonce } from './utils/nonce';
export function middleware(request: NextRequest) {
// Generate unique nonce for each request
const nonce = generateNonce();
// Important: Store nonce in headers to access it throughout the request lifecycle
const requestHeaders = new Headers(request.headers);
requestHeaders.set('x-nonce', nonce);
// Important: Define CSP directives with nonce
// Each directive controls different resource types
const cspHeader = `
default-src 'self';
script-src 'self' 'nonce-${nonce}'; // Only allow scripts with matching nonce
style-src 'self' 'nonce-${nonce}'; // Only allow styles with matching nonce
img-src 'self';
font-src 'self';
object-src 'none';
base-uri 'self';
form-action 'self';
frame-ancestors 'none';
upgrade-insecure-requests;
`.replace(/\s{2,}/g, ' ').trim();
const response = NextResponse.next({
request: {
headers: requestHeaders,
},
});
// Set the CSP header in the response
response.headers.set('Content-Security-Policy', cspHeader);
return response;
}
The middleware does several important things:
Generates a new nonce for each request
Sets up CSP directives that control what resources can be loaded
Passes the nonce through headers so it's available throughout the application
Server-Side Access Only
Remember that headers.get('x-nonce') only works in Server Components or server-side code. Client Components cannot access these headers directly.
If you're using Pages Router or need more control over the document structure, modify your `pages/_document.tsx` file. This file is crucial for applying the nonce to initial HTML and scripts:
// pages/_document.tsx
import Document, { Html, Head, Main, NextScript } from 'next/document'
class MyDocument extends Document {
// Important: Get nonce from request headers in getInitialProps
static async getInitialProps(ctx) {
const initialProps = await Document.getInitialProps(ctx)
const { req } = ctx
// Note: This only works server-side
const nonce = req ? req.headers['x-nonce'] : ''
return { ...initialProps, nonce }
}
render() {
const { nonce } = this.props as any
return (
<Html lang="en">
{/* Important: Apply nonce to Head for all initial styles */}
<Head nonce={nonce}>
<meta charSet="UTF-8" />
<link rel="icon" href="/favicon.ico" />
{/* Example: Inline script must include nonce */}
<script
nonce={nonce}
dangerouslySetInnerHTML={{
__html: `
// Your inline JavaScript here
console.log('Initialized with nonce');
`
}}
/>
</Head>
<body>
<Main />
{/* Important: Apply nonce to NextScript for all Next.js scripts */}
<NextScript nonce={nonce} />
</body>
</Html>
)
}
}
Modify your `pages/_app.tsx` to handle nonce with providers like Chakra UI, Emotion, or other third-party libraries. This setup ensures that all styles and scripts from these libraries are properly nonced:
// pages/_app.tsx
import { ChakraProvider } from "@chakra-ui/react";
import createCache from "@emotion/cache";
import { CacheProvider } from "@emotion/react";
import { ReCaptchaProvider } from "next-recaptcha-v3";
function App({ Component, pageProps, nonce }) {
// Important: Create Emotion cache with nonce for styled components
const cache = createCache({
key: "css",
nonce: nonce
});
return (
<CacheProvider value={cache}>
<ChakraProvider>
{/* Important: Pass nonce to third-party providers that support it */}
<ReCaptchaProvider
nonce={nonce}
reCaptchaKey="your-recaptcha-key"
>
<Component {...pageProps} />
</ReCaptchaProvider>
</ChakraProvider>
</CacheProvider>
);
}
// Important: Get nonce from request headers in getInitialProps
App.getInitialProps = async ({ ctx }) => {
const { req } = ctx;
// Note: This only works server-side
const nonce = req ? req.headers["x-nonce"] : "";
return { nonce };
};
export default App;
When using Next.js's Script component, you can pass the nonce as a prop. This is particularly useful for loading external scripts safely:
import Script from 'next/script';
import { headers } from 'next/headers';
// Important: This must be a Server Component to access headers
export default function Page() {
// Note: headers() only works in Server Components
const nonce = headers().get('x-nonce');
return (
<div>
{/* Apply nonce to Script component for security */}
<Script
src="/your-script.js"
nonce={nonce}
/>
</div>
);
}
The nonce value must be unique for each request
CSP headers should be tested thoroughly in development
Consider using report-only mode initially: Content-Security-Policy-Report-Only
Some third-party scripts might require additional CSP directives
Remember to add any external domains you need to the appropriate CSP directives
This setup provides a strong security foundation for your Next.js application while maintaining functionality. Test thoroughly in your development environment before deploying to production.
Learn more about unsafe-inline and how to properly setup your CSP to avoid using it.
2024-12-03
5 min read
Theotime Quere
Read more →
Understanding the difference between enforce and report only modes in Content-Security-Policy implementation.
2024-11-18
5 min read
Theotime Quere
Read more →
Main menu
All articles
Written by
Theotime Quere
CentralSaaS © 2025