Next.js Integration
Complete guide for integrating CSP Kit with Next.js applications, covering both App Router and Pages Router.
๐ Quick Startโ
Installationโ
npm install @csp-kit/generator @csp-kit/data
Basic Setupโ
// lib/csp.js
import { generateCSP } from '@csp-kit/generator';
export const cspConfig = generateCSP([
'google-analytics',
'vercel-analytics',
'google-fonts'
]);
๐ฑ App Router (Next.js 13+)โ
Method 1: Middleware (Recommended)โ
Create middleware.js
in your project root:
// middleware.js
import { NextResponse } from 'next/server';
import { generateCSPHeader } from '@csp-kit/generator';
export function middleware(request) {
// Generate CSP header
const csp = generateCSPHeader([
'google-analytics',
'vercel-analytics',
'google-fonts'
]);
// Create response
const response = NextResponse.next();
// Add CSP header
response.headers.set('Content-Security-Policy', csp);
return response;
}
export const config = {
matcher: [
/*
* Match all request paths except for the ones starting with:
* - api (API routes)
* - _next/static (static files)
* - _next/image (image optimization files)
* - favicon.ico (favicon file)
*/
'/((?!api|_next/static|_next/image|favicon.ico).*)',
],
};
Method 2: Route Headersโ
// app/layout.js
import { headers } from 'next/headers';
import { generateCSP } from '@csp-kit/generator';
export default function RootLayout({ children }) {
return (
<html lang="en">
<head>
<CSPMeta />
</head>
<body>{children}</body>
</html>
);
}
function CSPMeta() {
const result = generateCSP([
'google-analytics',
'vercel-analytics'
]);
return (
<meta
httpEquiv="Content-Security-Policy"
content={result.header}
/>
);
}
Method 3: API Routeโ
// app/api/csp/route.js
import { generateCSP } from '@csp-kit/generator';
import { NextResponse } from 'next/server';
export async function GET() {
const result = generateCSP([
'google-analytics',
'stripe',
'google-fonts'
]);
return NextResponse.json({
csp: result.header,
services: result.includedServices,
warnings: result.warnings
});
}
๐ Pages Router (Next.js 12 and below)โ
Method 1: Custom Documentโ
// pages/_document.js
import Document, { Html, Head, Main, NextScript } from 'next/document';
import { generateCSP } from '@csp-kit/generator';
class MyDocument extends Document {
static async getInitialProps(ctx) {
const initialProps = await Document.getInitialProps(ctx);
// Generate CSP
const cspResult = generateCSP([
'google-analytics',
'google-fonts',
'vercel-analytics'
]);
// Add CSP header if we have access to res
if (ctx.res) {
ctx.res.setHeader('Content-Security-Policy', cspResult.header);
}
return {
...initialProps,
csp: cspResult.header
};
}
render() {
return (
<Html>
<Head>
{this.props.csp && (
<meta
httpEquiv="Content-Security-Policy"
content={this.props.csp}
/>
)}
</Head>
<body>
<Main />
<NextScript />
</body>
</Html>
);
}
}
export default MyDocument;
Method 2: API Middlewareโ
// pages/api/_middleware.js (Next.js 12)
import { NextResponse } from 'next/server';
import { generateCSPHeader } from '@csp-kit/generator';
export function middleware(req) {
const csp = generateCSPHeader(['google-analytics', 'stripe']);
const response = NextResponse.next();
response.headers.set('Content-Security-Policy', csp);
return response;
}
Method 3: Custom App with getInitialPropsโ
// pages/_app.js
import App from 'next/app';
import { generateCSP } from '@csp-kit/generator';
class MyApp extends App {
static async getInitialProps({ Component, ctx }) {
let pageProps = {};
if (Component.getInitialProps) {
pageProps = await Component.getInitialProps(ctx);
}
// Set CSP header on server side
if (ctx.res) {
const cspResult = generateCSP([
'google-analytics',
'google-fonts'
]);
ctx.res.setHeader('Content-Security-Policy', cspResult.header);
}
return { pageProps };
}
render() {
const { Component, pageProps } = this.props;
return <Component {...pageProps} />;
}
}
export default MyApp;
๐ง Advanced Configurationsโ
Dynamic CSP Based on Routeโ
// middleware.js
import { NextResponse } from 'next/server';
import { generateCSPHeader } from '@csp-kit/generator';
export function middleware(request) {
const url = request.nextUrl.clone();
let services = ['google-analytics', 'google-fonts']; // Base services
// Add services based on route
if (url.pathname.startsWith('/checkout')) {
services.push('stripe');
}
if (url.pathname.startsWith('/contact')) {
services.push('typeform');
}
if (url.pathname.startsWith('/help')) {
services.push('intercom');
}
const csp = generateCSPHeader(services);
const response = NextResponse.next();
response.headers.set('Content-Security-Policy', csp);
return response;
}
Environment-Based CSPโ
// lib/csp.js
import { generateCSP } from '@csp-kit/generator';
const getCSPForEnvironment = () => {
const baseServices = ['google-fonts'];
if (process.env.NODE_ENV === 'development') {
// More permissive for development
return generateCSP({
services: [...baseServices, 'localhost'],
customRules: {
'script-src': ["'unsafe-eval'"], // For hot reloading
'style-src': ["'unsafe-inline'"] // For styled-components
}
});
}
if (process.env.NODE_ENV === 'production') {
// Strict for production
return generateCSP([
...baseServices,
'google-analytics',
'vercel-analytics'
]);
}
// Staging environment
return generateCSP({
services: [...baseServices, 'google-analytics'],
reportUri: 'https://staging.example.com/csp-report'
});
};
export const cspConfig = getCSPForEnvironment();
CSP with Nonce for Inline Scriptsโ
// app/layout.js
import { generateCSP } from '@csp-kit/generator';
export default function RootLayout({ children }) {
const cspResult = generateCSP({
services: ['google-analytics'],
nonce: true
});
return (
<html lang="en">
<head>
<meta
httpEquiv="Content-Security-Policy"
content={cspResult.header}
/>
</head>
<body>
{children}
{/* Use nonce for inline scripts */}
<script nonce={cspResult.nonce}>
{`
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', 'GA_MEASUREMENT_ID');
`}
</script>
</body>
</html>
);
}
๐ฏ Common Use Casesโ
E-commerce Siteโ
// middleware.js - E-commerce CSP
import { generateCSPHeader } from '@csp-kit/generator';
export function middleware(request) {
const csp = generateCSPHeader([
'google-analytics', // Analytics
'facebook-pixel', // Marketing
'stripe', // Payments
'google-fonts', // Typography
'intercom', // Customer support
'hotjar', // User experience
'mailchimp', // Email marketing
'google-ads' // Advertising
]);
const response = NextResponse.next();
response.headers.set('Content-Security-Policy', csp);
return response;
}
SaaS Applicationโ
// lib/csp-saas.js
import { generateCSP } from '@csp-kit/generator';
export const saasCsp = generateCSP({
services: [
'google-analytics', // Product analytics
'mixpanel', // Event tracking
'sentry', // Error monitoring
'intercom', // Customer support
'stripe', // Billing
'auth0', // Authentication
'google-fonts' // Typography
],
customRules: {
'connect-src': [
'https://api.yoursaas.com', // Your API
'wss://realtime.yoursaas.com' // WebSocket
]
},
reportUri: 'https://yoursaas.com/csp-report'
});
Blog/Content Siteโ
// Blog CSP configuration
import { generateCSPHeader } from '@csp-kit/generator';
const blogCsp = generateCSPHeader([
'google-analytics', // Analytics
'google-fonts', // Typography
'youtube', // Video embeds
'twitter', // Tweet embeds
'instagram', // Instagram embeds
'google-ads', // Monetization
'disqus' // Comments (if supported)
]);
๐ Security Best Practicesโ
Report-Only Mode for Testingโ
// middleware.js - Test CSP without breaking site
import { generateReportOnlyCSPAsync } from '@csp-kit/generator';
export async function middleware(request) {
const reportOnlyCsp = await generateReportOnlyCSPAsync([
'google-analytics',
'new-service-to-test'
]);
const response = NextResponse.next();
response.headers.set('Content-Security-Policy-Report-Only', reportOnlyCsp);
return response;
}
CSP Violation Reportingโ
// pages/api/csp-report.js
export default function handler(req, res) {
if (req.method === 'POST') {
const report = req.body;
// Log CSP violations
console.error('CSP Violation:', {
document: report['document-uri'],
violated: report['violated-directive'],
blocked: report['blocked-uri'],
source: report['source-file'],
line: report['line-number']
});
// Optionally send to monitoring service
// await sendToMonitoring(report);
res.status(204).end();
} else {
res.status(405).end();
}
}
Gradual CSP Rolloutโ
// Gradually enable strict CSP
import { generateCSP } from '@csp-kit/generator';
const getCSPStrictness = () => {
const rolloutPercentage = 10; // 10% of users get strict CSP
const userId = getUserId(); // Your user identification logic
const isInRollout = (userId % 100) < rolloutPercentage;
if (isInRollout) {
return generateCSP(['google-analytics']); // Strict
} else {
return generateCSP({
services: ['google-analytics'],
unsafeInline: true // Less strict
});
}
};
๐ Performance Optimizationโ
Cached CSP Generationโ
// lib/csp-cache.js
import { generateCSP } from '@csp-kit/generator';
const cspCache = new Map();
export function getCachedCSP(services) {
const key = services.sort().join(',');
if (!cspCache.has(key)) {
const result = generateCSP(services);
cspCache.set(key, result);
}
return cspCache.get(key);
}
// Clear cache periodically
setInterval(() => {
cspCache.clear();
}, 60 * 60 * 1000); // 1 hour
Edge Runtime Compatibleโ
// middleware.js - Works with Edge Runtime
import { generateCSPHeader } from '@csp-kit/generator';
export const config = {
runtime: 'edge',
};
export function middleware(request) {
// Works in Vercel Edge Functions
const csp = generateCSPHeader(['google-analytics', 'vercel-analytics']);
const response = NextResponse.next();
response.headers.set('Content-Security-Policy', csp);
return response;
}
๐งช Testingโ
Test CSP Configurationโ
// __tests__/csp.test.js
import { generateCSP } from '@csp-kit/generator';
describe('CSP Configuration', () => {
test('generates valid CSP for production', () => {
const result = generateCSP([
'google-analytics',
'stripe',
'google-fonts'
]);
expect(result.header).toContain("script-src 'self'");
expect(result.includedServices).toHaveLength(3);
expect(result.unknownServices).toHaveLength(0);
});
test('includes nonce when requested', () => {
const result = generateCSP({
services: ['google-analytics'],
nonce: true
});
expect(result.nonce).toBeDefined();
expect(result.header).toContain(`'nonce-${result.nonce}'`);
});
});
E2E Testing with CSPโ
// cypress/e2e/csp.cy.js
describe('CSP Compliance', () => {
it('should not have CSP violations', () => {
cy.visit('/');
// Check for CSP violations in console
cy.window().then((win) => {
cy.spy(win.console, 'error').as('consoleError');
});
// Interact with features that use external scripts
cy.get('[data-testid="analytics-button"]').click();
// Should not have CSP violation errors
cy.get('@consoleError').should('not.have.been.calledWith',
Cypress.sinon.match(/Content Security Policy/)
);
});
});
๐จ Troubleshootingโ
Common Issuesโ
1. CSP blocking development tools
// Add development-specific rules
const isDev = process.env.NODE_ENV === 'development';
const csp = generateCSP({
services: ['google-analytics'],
customRules: isDev ? {
'script-src': ["'unsafe-eval'"], // For hot reloading
'connect-src': ['webpack://', 'ws://localhost:*']
} : {}
});
2. CSP blocking styled-components
// Add nonce support for styled-components
import { ServerStyleSheet } from 'styled-components';
const sheet = new ServerStyleSheet();
const csp = generateCSP({
services: ['google-analytics'],
nonce: true
});
// Use nonce in styled-components
sheet.collectStyles(<App nonce={csp.nonce} />);
3. CSP violations in production
// Enable reporting to debug issues
const csp = generateCSP({
services: ['google-analytics', 'stripe'],
reportUri: 'https://yourdomain.com/api/csp-report'
});
Debug CSP Generationโ
// lib/debug-csp.js
import { generateCSP } from '@csp-kit/generator';
export function debugCSP(services) {
const result = generateCSP(services);
console.log('๐ CSP Debug Info:');
console.log('Services:', result.includedServices);
console.log('Unknown:', result.unknownServices);
console.log('Warnings:', result.warnings);
console.log('Header:', result.header);
return result;
}
// Usage in development
if (process.env.NODE_ENV === 'development') {
debugCSP(['google-analytics', 'stripe']);
}
๐ Next.js Specific Resourcesโ
๐ Related Examplesโ
More framework examples coming soon! Contributions welcome.