CSP là lớp phòng thủ thứ hai sau escaping: kể cả khi payload lọt vào DOM, trình duyệt vẫn từ chối thực thi script không được cho phép.
Vấn đề với framework SSR là trang luôn có inline script (hydration data, <Script> của Next). Nếu mở script-src 'unsafe-inline' thì CSP mất tác dụng chống XSS — payload <script> do kẻ tấn công chèn cũng là inline script hợp lệ.
Cách đúng: nonce sinh mới mỗi request. Trong Next.js sinh ở middleware rồi đọc lại trong Server Component:
// middleware.ts
const nonce = Buffer.from(crypto.randomUUID()).toString('base64')
const csp = [
"default-src 'self'",
`script-src 'self' 'nonce-${nonce}' 'strict-dynamic'`,
"style-src 'self' 'unsafe-inline'",
"img-src 'self' data: https:",
"connect-src 'self'",
"frame-ancestors 'none'",
"base-uri 'self'",
"object-src 'none'",
].join('; ')Gắn nonce vào response header và header x-nonce để component đọc qua headers() rồi truyền vào <Script nonce={nonce}>.
Những điểm hay bị hỏi thêm:
- Nonce phải ngẫu nhiên mỗi request; nonce cố định trong build là vô nghĩa.
- Có nonce thì trang không thể là static — phải render động.
- style-src thường vẫn phải để 'unsafe-inline' vì CSS-in-JS; chấp nhận được vì rủi ro thấp hơn nhiều so với script.
- Triển khai dần bằng Content-Security-Policy-Report-Only + endpoint nhận report, xem log vài ngày rồi mới bật chế độ chặn.