CSP Headers
Resolve Content Security Policy violations and script blocking
CSP violations prevent resource loading but provide exact diagnostic information. Check browser console for violation reports, then adjust CSP configuration in packages/app-shell/src/security/csp.ts
.
Scripts Blocked by CSP
Symptoms: Browser console shows CSP violation. Scripts fail to load.
Diagnosis: Script source not whitelisted or missing nonce attribute.
Solution:
Dashboard app uses nonce-based CSP. Add nonce to custom scripts:
import { headers } from "next/headers";
export default async function Layout({ children }) {
const headersList = await headers();
const nonce = headersList.get("x-nonce");
return (
<html>
<head>
<script src="/analytics.js" nonce={nonce} />
</head>
<body>{children}</body>
</html>
);
}
For external scripts, add domain to CSP builder:
const CONNECT_SRC_BASE = [
"'self'",
"https://*.convex.cloud",
"wss://*.convex.cloud",
"https://analytics.example.com", // Add here
];
Images Not Loading
Symptoms: Images from external domains blocked. Console shows img-src violations.
Diagnosis: Image domain not whitelisted in CSP.
Solution:
Add image domains to CSP builder:
const IMG_SRC = [
"'self'",
"data:",
"blob:",
"https://lh3.googleusercontent.com",
"https://your-cdn.example.com", // Add here
];
Also add to Next.js image configuration:
images: {
remotePatterns: [
{ protocol: "https", hostname: "your-cdn.example.com" },
],
}
Fonts Not Loading
Symptoms: Fonts from Google Fonts or external CDNs blocked.
Diagnosis: Font domain not in font-src directive.
Solution:
Google Fonts already whitelisted. For additional font providers:
const FONT_SRC = [
"'self'",
"https://fonts.gstatic.com",
"data:",
"https://fonts.example.com", // Add custom font CDN
];
WebSocket Connection Blocked
Symptoms: Convex real-time updates fail. Console shows connect-src violations.
Diagnosis: WebSocket URL not in connect-src directive.
Solution:
Convex domains already whitelisted (wss://*.convex.cloud
). For additional WebSocket connections:
const CONNECT_SRC_BASE = [
"'self'",
"https://*.convex.cloud",
"wss://*.convex.cloud",
"wss://realtime.example.com", // Add custom WebSocket
];
Frame-Ancestors Blocking Embed
Symptoms: Application cannot be embedded in iframe.
Diagnosis: CSP sets frame-ancestors 'none'
which blocks all embedding.
Solution:
// Change from:
["frame-ancestors", ["'none'"]],
// To allow specific embedders:
["frame-ancestors", ["'self'", "https://trusted-embedder.com"]],
Heads up
Only allow trusted domains. frame-ancestors
prevents clickjacking attacks.
Inline Styles Blocked in Production
Symptoms: Tailwind classes work in development but fail in production.
Diagnosis: Dashboard production CSP requires nonce for inline styles.
Solution:
Dashboard CSP allows style-src-attr 'unsafe-inline'
for Tailwind and motion libraries. Inline <style>
tags require nonce:
const nonce = headersList.get("x-nonce");
<style nonce={nonce}>{`.custom { color: blue; }`}</style>
Marketing app allows all inline styles via 'unsafe-inline'
.
Related Documentation
- Security Headers - Complete CSP configuration
- Hybrid CSP - Nonce-based vs static CSP