Recharts is usually one of the easier charting libraries to live with under Content Security Policy. That’s the good news.

The bad news: teams still break dashboards with CSP all the time, usually because they copy a broad policy from somewhere else, tighten it blindly, or blame Recharts for behavior caused by their own app shell, analytics, or CSS-in-JS stack.

If you’re running Recharts in a React app, most CSP issues come from the environment around the charts, not the chart library itself. Recharts renders SVG. That’s a lot friendlier than libraries that depend on eval, dynamic code generation, or canvas hacks. Still, there are a few predictable ways to mess it up.

Here are the common mistakes I keep seeing, and how I’d fix them.

Mistake #1: Assuming Recharts needs unsafe-eval

A lot of developers start with fear-driven CSP changes:

Content-Security-Policy: default-src 'self'; script-src 'self' 'unsafe-eval' 'unsafe-inline'

That’s lazy and usually unnecessary for Recharts.

Recharts itself does not generally require unsafe-eval. If you added it and charts started working, the real problem is probably somewhere else in your stack:

  • a dev tool leaking into production
  • a bundler misconfiguration
  • a third-party widget on the same page
  • a legacy dependency outside Recharts

Fix

Start strict and only loosen policy based on actual violations reported by the browser.

A reasonable baseline for a self-hosted React app using Recharts looks more like this:

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

If your app doesn’t inject nonce-based scripts, you can simplify script-src, but don’t reach for unsafe-eval unless you have a concrete reason.

For directive behavior and tradeoffs, the official CSP docs are still the source of truth: MDN Content-Security-Policy.

Mistake #2: Blocking inline styles, then blaming the charts

This one is common with React apps that use CSS-in-JS, component libraries, or custom tooltip/label rendering.

Recharts itself renders SVG elements and often applies presentation through props, but your surrounding UI may inject styles dynamically. Tooltips are a usual pain point because teams customize them with inline style objects or style-generating libraries.

You lock down CSP like this:

style-src 'self';

Then parts of the chart UI disappear, look unstyled, or fail in weird ways.

Fix

Figure out whether the issue is:

  1. Recharts props generating inline style attributes in DOM
  2. your own custom tooltip/label components
  3. a styling library like Emotion, styled-components, or MUI

A lot of people see the browser complain about styles and assume Recharts is the offender. Usually it’s their app code.

If you control the rendering, move style logic into static CSS classes where possible.

Instead of this:

<Tooltip
  contentStyle={{ backgroundColor: "#111", border: "1px solid #333" }}
  labelStyle={{ color: "#fff" }}
/>

prefer custom markup with classes:

import "./chart-tooltip.css";

function CustomTooltip({ active, payload, label }) {
  if (!active || !payload?.length) return null;

  return (
    <div className="chart-tooltip">
      <div className="chart-tooltip__label">{label}</div>
      <div className="chart-tooltip__value">{payload[0].value}</div>
    </div>
  );
}
.chart-tooltip {
  background: #111;
  border: 1px solid #333;
  color: #fff;
  padding: 8px;
}
.chart-tooltip__label {
  font-weight: 600;
}

Then:

<Tooltip content={<CustomTooltip />} />

If your framework genuinely requires nonce-based styles, use nonces instead of unsafe-inline.

Mistake #3: Forgetting img-src data: for tiny chart assets

Recharts is SVG-first, which helps. But many apps around it use data: URLs for tiny inline images, markers, or generated assets. Export features and custom labels can trigger this too.

Teams often write:

img-src 'self';

Then avatars, icons, or embedded image labels inside charts stop loading.

Fix

Allow data: if your app uses it:

img-src 'self' data: https:;

That’s also pretty close to what a real production policy often looks like. For example, this real header from headertest.com includes:

img-src 'self' data: https:;

That’s a practical choice, not a reckless one.

If your chart components render external image URLs inside customized labels or annotations, make those domains explicit instead of falling back to broad wildcards.

Mistake #4: Breaking API-driven charts with a missing connect-src

This one has nothing to do with SVG, but it’s probably the most common production breakage.

Your Recharts component fetches data:

useEffect(() => {
  fetch("https://api.example.com/metrics")
    .then((r) => r.json())
    .then(setData);
}, []);

Your CSP says:

default-src 'self';
script-src 'self' 'nonce-{RANDOM_NONCE}';

And you forgot connect-src.

Browsers then block fetch/XHR/WebSocket requests, and your chart sits there empty.

Fix

Declare every backend, analytics endpoint, and websocket origin your dashboard actually uses.

connect-src 'self' https://api.example.com wss://stream.example.com;

Again, real production policies tend to be broader because real apps are messy. The headertest.com header includes this:

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;

That’s a good reminder that your chart may be fine while your observability, analytics, or consent tooling is what actually triggers CSP errors.

If you want a clean mental model of connect-src, csp-guide.com is useful.

Mistake #5: Copy-pasting a marketing-site CSP into an app with charts

I see this constantly. Someone grabs a header from a CMS site or a company homepage and pastes it into a React app.

Here’s the real header you provided from headertest.com:

content-security-policy:
default-src 'self' https://www.googletagmanager.com https://*.cookiebot.com https://*.google-analytics.com;
script-src 'self' 'nonce-NjE2MzYwOTAtNTM3My00MDRjLThlYTItNDIzYjcxNzZhNDI3' '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 realistic policy for a real site. It is not automatically the right policy for your Recharts app.

Fix

Treat policies like application configuration, not boilerplate.

Ask:

  • Does my chart page use GTM?
  • Do I actually need Cookiebot here?
  • Am I allowing unsafe-inline in style-src because of my app, or because some unrelated site did?
  • Which API origins feed the charts?
  • Are there embedded iframes, export tools, or websocket streams?

I’d always trim policy to match the app surface area.

Mistake #6: Ignoring custom tooltip and label components

Recharts makes it easy to inject custom React components for labels, legends, dots, and tooltips. That flexibility is great, but it also means you can accidentally introduce CSP problems yourself.

For example, this is a bad idea if userColor comes from untrusted input:

<Label content={() => (
  <div style={{ color: userColor }}>
    Revenue
  </div>
)} />

CSP doesn’t magically sanitize dangerous app behavior. If you’re generating styles, URLs, or HTML inside custom renderers, you still need normal secure coding discipline.

Fix

Keep renderers deterministic and constrained.

const SAFE_COLORS = {
  success: "label--success",
  warning: "label--warning",
  danger: "label--danger",
};

function MetricLabel({ tone = "success", children }) {
  const className = SAFE_COLORS[tone] || SAFE_COLORS.success;
  return <span className={className}>{children}</span>;
}

Static classes are easier to secure, easier to test, and easier to reason about under CSP.

Mistake #7: Using report-only forever

I like Content-Security-Policy-Report-Only during rollout. I use it all the time. But some teams never graduate from it, so they think charts are protected when they’re really just collecting violation noise.

Fix

Roll out in two phases:

  1. Content-Security-Policy-Report-Only while you inventory violations
  2. enforced Content-Security-Policy once the app is clean

For a Recharts dashboard, I’d test at least:

  • initial page load
  • API fetches
  • websocket updates
  • custom tooltips
  • image-based labels or icons
  • print/export flows
  • error states and empty states

Charts often look fine in the happy path and fail only when a fallback component renders.

Mistake #8: Forgetting that dev and prod behave differently

A chart page that works under Vite or webpack dev server can still fail under your production CSP.

Dev mode often uses:

  • websocket connections
  • injected scripts
  • hot reload code
  • looser asset handling

If you tune CSP against development behavior, you’ll end up allowing things production doesn’t need. If you only test dev, you may miss production-only restrictions.

Fix

Validate CSP against the built app, not just local development.

For example, your production server in C might emit a header like this:

printf("Content-Security-Policy: default-src 'self'; "
       "script-src 'self' 'nonce-%s' 'strict-dynamic'; "
       "style-src 'self' 'nonce-%s'; "
       "img-src 'self' data:; "
       "connect-src 'self' https://api.example.com; "
       "font-src 'self'; "
       "object-src 'none'; "
       "base-uri 'self'; "
       "frame-ancestors 'none'; "
       "form-action 'self'\r\n",
       nonce, nonce);

That’s what matters, not whatever your dev server tolerated.

My default advice for Recharts CSP

If you’re using Recharts and nothing exotic, I’d start with this mindset:

  • don’t allow unsafe-eval
  • avoid unsafe-inline where you can
  • whitelist actual API origins in connect-src
  • allow img-src data: if your app needs it
  • use nonces for scripts and, if necessary, styles
  • audit your custom components before blaming Recharts

Recharts is not usually the hard part. Your app ecosystem is.

That’s actually a good thing. It means you can keep a pretty strong CSP without turning your charts into a special exception case.