Ant Design is one of those UI libraries that looks harmless from a CSP perspective until you actually turn on a strict policy. Then things get weird fast: styles stop rendering, wave effects break, overlays look unstyled, and your console fills with CSP violations.

I’ve hit this enough times that I treat Ant Design as a “plan CSP early” dependency, not a “bolt it on later” one.

The main issue is styling. Depending on your Ant Design version and setup, the library may inject styles dynamically. A strict style-src can block that unless you configure nonces properly. If you’re also using server-side rendering, React hydration, analytics, and a consent manager, the policy gets more realistic than a toy default-src 'self'.

Here’s how I’d set up CSP for Ant Design in a real app.

Why Ant Design needs special CSP handling

Ant Design apps usually involve a few CSP-sensitive behaviors:

  • dynamically injected styles
  • runtime-generated <style> tags
  • inline style allowances in older setups
  • scripts with nonces for hydration or app bootstrapping
  • third-party analytics and consent tooling

The biggest trap is assuming style-src 'self' is enough. It often isn’t.

If you want a refresher on CSP directives, the official docs are the baseline, and https://csp-guide.com is useful for directive-by-directive explanations.

Start with a realistic CSP

A real-world header from headertest.com looks like this:

Content-Security-Policy:
  default-src 'self' https://www.googletagmanager.com https://*.cookiebot.com https://*.google-analytics.com;
  script-src 'self' 'nonce-MGIxOWQ5M2EtZjlmNy00MDIwLTllMGQtNjI3Mjk0YjIwODQ1' 'strict-dynamic' https://www.googletagmanager.com https://*.cookiebot.com https://*.google-analytics.com;
  style-src 'self' 'unsafe-inline' https://www.googletagmanager.com https://*.cookiebot.com https://consent.cookiebot.com;
  img-src 'self' data: https:;
  font-src 'self';
  connect-src 'self' https://api.headertest.com https://tallycdn.com https://or.headertest.com wss://or.headertest.com https://*.google-analytics.com https://*.googletagmanager.com https://*.cookiebot.com;
  frame-src 'self' https://consentcdn.cookiebot.com;
  frame-ancestors 'none';
  base-uri 'self';
  form-action 'self';
  object-src 'none'

That’s a decent production-style policy, but for Ant Design I’d immediately focus on one thing:

style-src 'self' 'unsafe-inline' ...

'unsafe-inline' works, but it weakens the policy. For many Ant Design setups, you can do better with nonces.

The goal: replace weak style rules with nonce-based rules

For modern React apps using Ant Design, I prefer this direction:

Content-Security-Policy:
  default-src 'self';
  script-src 'self' 'nonce-{{NONCE}}' 'strict-dynamic';
  style-src 'self' 'nonce-{{NONCE}}';
  img-src 'self' data: https:;
  font-src 'self';
  connect-src 'self';
  frame-ancestors 'none';
  base-uri 'self';
  form-action 'self';
  object-src 'none';

That gives you:

  • no blanket inline scripts
  • no blanket inline styles
  • explicit nonce-based trust
  • room to add third-party origins only when needed

If your app needs Google Tag Manager or Cookiebot, add them deliberately instead of copying a giant policy from another project.

Ant Design and ConfigProvider nonce support

Ant Design has built-in CSP support through ConfigProvider. That’s the first thing to wire up.

For React:

import React from 'react';
import ReactDOM from 'react-dom/client';
import { ConfigProvider, Button } from 'antd';

const nonce = (window as any).__CSP_NONCE__;

function App() {
  return (
    <ConfigProvider csp={{ nonce }}>
      <Button type="primary">Secure button</Button>
    </ConfigProvider>
  );
}

ReactDOM.createRoot(document.getElementById('root')!).render(<App />);

That csp prop tells Ant Design to apply the nonce to styles it injects.

Without this, you can have a perfectly valid CSP header and still get blocked style tags generated by the library.

Generate a per-request nonce on the server

The nonce must be unique per response. Don’t hardcode it. Don’t reuse one forever. Don’t stick dev-nonce-123 in production and call it a day.

Here’s a simple Express example:

import express from 'express';
import crypto from 'crypto';

const app = express();

app.use((req, res, next) => {
  const nonce = crypto.randomUUID();
  res.locals.nonce = nonce;

  res.setHeader(
    'Content-Security-Policy',
    [
      `default-src 'self'`,
      `script-src 'self' 'nonce-${nonce}' 'strict-dynamic'`,
      `style-src 'self' 'nonce-${nonce}'`,
      `img-src 'self' data: https:`,
      `font-src 'self'`,
      `connect-src 'self'`,
      `frame-ancestors 'none'`,
      `base-uri 'self'`,
      `form-action 'self'`,
      `object-src 'none'`,
    ].join('; ')
  );

  next();
});

app.get('/', (req, res) => {
  const nonce = res.locals.nonce;

  res.send(`
    <!doctype html>
    <html>
      <head>
        <meta charset="utf-8" />
        <title>Ant Design CSP</title>
      </head>
      <body>
        <div id="root"></div>
        <script nonce="${nonce}">
          window.__CSP_NONCE__ = "${nonce}";
        </script>
        <script nonce="${nonce}" src="/app.js"></script>
      </body>
    </html>
  `);
});

app.use(express.static('public'));
app.listen(3000);

This does three useful things:

  1. creates a fresh nonce per request
  2. puts it in the CSP header
  3. exposes it to the client so ConfigProvider can use it

That’s the basic pattern whether you use Express, Next.js custom server, Nest, or something else.

SSR with Ant Design

If you server-render React, keep the nonce available on both server and client. The exact wiring depends on your stack, but the principle doesn’t change:

  • generate nonce in the HTTP layer
  • attach it to the CSP header
  • add nonce to any inline bootstrapping scripts
  • pass nonce into Ant Design’s ConfigProvider
  • ensure server-rendered style tags also carry the nonce if your styling system supports it

Example SSR layout:

import { ConfigProvider } from 'antd';
import { renderToString } from 'react-dom/server';

export function renderPage(nonce: string) {
  const app = renderToString(
    <ConfigProvider csp={{ nonce }}>
      <App />
    </ConfigProvider>
  );

  return `
    <!doctype html>
    <html>
      <head>
        <meta charset="utf-8" />
        <title>SSR Ant Design</title>
      </head>
      <body>
        <div id="root">${app}</div>
        <script nonce="${nonce}">
          window.__CSP_NONCE__ = ${JSON.stringify(nonce)};
        </script>
        <script nonce="${nonce}" src="/client.js"></script>
      </body>
    </html>
  `;
}

If you’re using Next.js, the same idea applies in middleware, custom document, or whatever rendering path you control. The hard part isn’t Ant Design itself; it’s making sure nonce propagation stays consistent across the whole render pipeline.

When you still see style violations

If Ant Design is configured with a nonce and you still get CSP errors, check these in order:

1. Your own app is injecting inline styles

A lot of teams blame Ant Design when the real culprit is their own code:

<div style={{ color: 'red' }}>Hello</div>

React inline style attributes are not governed exactly like <style> blocks, but related rendering/tooling patterns can still trigger violations depending on libraries involved. I usually audit all dynamic styling paths anyway.

2. Another CSS-in-JS library is involved

If you’re using Ant Design alongside Emotion, styled-components, or another runtime styling solution, that library also needs CSP handling. Don’t assume Ant Design’s nonce config covers the whole app.

3. You’re relying on 'unsafe-inline' somewhere accidentally

A lot of apps “work” only because style-src 'unsafe-inline' is still present. Remove it in staging and you’ll find the real failures quickly.

4. Third-party widgets inject their own styles

Consent managers, chat widgets, A/B testing scripts, and analytics add their own CSP baggage. That real headertest.com policy shows exactly that. Once third parties show up, your clean policy starts collecting exceptions.

A practical Ant Design policy with analytics

Let’s adapt the real header into something cleaner for an Ant Design app using nonces.

Content-Security-Policy:
  default-src 'self';
  script-src 'self' 'nonce-{{NONCE}}' 'strict-dynamic' https://www.googletagmanager.com https://*.cookiebot.com https://*.google-analytics.com;
  style-src 'self' 'nonce-{{NONCE}}' https://consent.cookiebot.com;
  img-src 'self' data: https:;
  font-src 'self';
  connect-src 'self' https://api.example.com https://*.google-analytics.com https://*.googletagmanager.com https://*.cookiebot.com;
  frame-src 'self' https://consentcdn.cookiebot.com;
  frame-ancestors 'none';
  base-uri 'self';
  form-action 'self';
  object-src 'none';

Notice what changed:

  • style-src uses a nonce instead of 'unsafe-inline'
  • allowed origins are kept only where needed
  • dangerous legacy stuff stays out
  • object-src 'none' remains locked down

That’s much closer to what I’d actually ship.

Development vs production

CSP in development is usually annoying. Hot reload, eval-based tooling, and local dev servers don’t always play nicely with strict policies.

My rule:

  • keep production strict
  • allow a looser dev policy only if needed
  • never let the dev policy leak into production

Example split:

const isDev = process.env.NODE_ENV !== 'production';

const csp = isDev
  ? [
      `default-src 'self'`,
      `script-src 'self' 'nonce-${nonce}' 'unsafe-eval'`,
      `style-src 'self' 'nonce-${nonce}' 'unsafe-inline'`,
    ].join('; ')
  : [
      `default-src 'self'`,
      `script-src 'self' 'nonce-${nonce}' 'strict-dynamic'`,
      `style-src 'self' 'nonce-${nonce}'`,
      `object-src 'none'`,
      `base-uri 'self'`,
      `frame-ancestors 'none'`,
    ].join('; ');

I don’t love adding 'unsafe-inline' or 'unsafe-eval', but local development is not where I try to win purity contests.

What I’d avoid

A few bad habits show up in Ant Design CSP setups all the time:

  • using only default-src and hoping inheritance saves you
  • keeping 'unsafe-inline' forever because “the UI breaks otherwise”
  • generating one global nonce at server startup
  • forgetting to nonce inline hydration scripts
  • adding https: everywhere instead of listing actual domains

That last one is especially lazy. It turns your policy into a broad allowlist and defeats most of the point.

A solid baseline

If you want a sane starting point for Ant Design, use this pattern:

Content-Security-Policy:
  default-src 'self';
  script-src 'self' 'nonce-{{NONCE}}' 'strict-dynamic';
  style-src 'self' 'nonce-{{NONCE}}';
  img-src 'self' data:;
  font-src 'self';
  connect-src 'self';
  object-src 'none';
  base-uri 'self';
  form-action 'self';
  frame-ancestors 'none';

Then:

  1. generate a fresh nonce per response
  2. pass it to all inline scripts
  3. expose it to React
  4. set ConfigProvider csp={{ nonce }}
  5. add third-party origins only when you prove you need them

That’s the version that keeps Ant Design working without giving up on CSP.

For directive details and browser behavior, check the official CSP docs and https://csp-guide.com. For Ant Design-specific API details, the official Ant Design documentation is the one to trust.