Visx is one of those rare charting libraries I actually like using. It gives you low-level control, stays close to SVG, and doesn’t drag in a giant black-box runtime. From a CSP perspective, that’s good news.
The short version: Visx itself is usually CSP-friendly. It renders React components into SVG and doesn’t need eval, inline scripts, or weird dynamic code loading. Most CSP breakage around Visx comes from the app around it: tooltips, inline styles, custom HTML labels, analytics, and dev tooling.
If you want a quick CSP sanity check for your app headers, headertest.com is handy.
The baseline: what Visx usually needs
For a production React app using Visx:
script-src 'self'is usually enough for your own bundle- no
unsafe-evalneeded for Visx - no
unsafe-inlineneeded for scripts if your app is built correctly style-srcdepends on how your app handles styles, not Visx specificallyimg-srcmay needdata:if you embed images or use data URLsconnect-srcmatters only if your charts fetch remote data
A minimal starting policy for a self-hosted app with Visx looks like this:
Content-Security-Policy:
default-src 'self';
script-src 'self';
style-src 'self';
img-src 'self' data:;
font-src 'self';
connect-src 'self';
object-src 'none';
base-uri 'self';
frame-ancestors 'none';
form-action 'self';
If your app works with that, great. Keep it.
Copy-paste CSP for Visx in a React app
This is the policy I’d start with for a typical production React app that renders Visx charts and fetches API data from the same origin.
Content-Security-Policy:
default-src 'self';
script-src 'self';
style-src 'self';
img-src 'self' data:;
font-src 'self';
connect-src 'self';
object-src 'none';
base-uri 'self';
frame-ancestors 'none';
form-action 'self';
Nginx
add_header Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self'; img-src 'self' data:; font-src 'self'; connect-src 'self'; object-src 'none'; base-uri 'self'; frame-ancestors 'none'; form-action 'self';" always;
Apache
Header always set Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self'; img-src 'self' data:; font-src 'self'; connect-src 'self'; object-src 'none'; base-uri 'self'; frame-ancestors 'none'; form-action 'self';"
Express + Helmet
import express from "express";
import helmet from "helmet";
const app = express();
app.use(
helmet({
contentSecurityPolicy: {
directives: {
defaultSrc: ["'self'"],
scriptSrc: ["'self'"],
styleSrc: ["'self'"],
imgSrc: ["'self'", "data:"],
fontSrc: ["'self'"],
connectSrc: ["'self'"],
objectSrc: ["'none'"],
baseUri: ["'self'"],
frameAncestors: ["'none'"],
formAction: ["'self'"],
},
},
})
);
Does Visx require unsafe-inline?
Usually, no.
Visx components mostly set presentation through React props and SVG attributes:
<Bar
x={10}
y={20}
width={40}
height={100}
fill="#4f46e5"
/>
That doesn’t require inline script permissions.
Where people get tripped up is inline styles in React:
<div style={{ position: "absolute", left: 10, top: 20 }}>
Tooltip
</div>
Depending on browser behavior and your setup, CSP style-src can affect these patterns. If your tooltip layer or chart wrapper relies heavily on inline styles, you may end up needing:
style-src 'self' 'unsafe-inline';
I try hard to avoid that in production. Prefer CSS classes over inline style objects where possible.
Example:
<div className="chart-tooltip">Revenue: $42k</div>
.chart-tooltip {
position: absolute;
pointer-events: none;
background: #111827;
color: white;
padding: 8px 10px;
border-radius: 6px;
}
Then your CSP can stay tighter:
style-src 'self';
If you want a deeper breakdown of how style-src behaves, unsafe-inline, hashes, and nonces, csp-guide.com is a good reference.
Tooltip-heavy Visx apps: the real CSP pain point
A lot of Visx chart UIs use @visx/tooltip and position tooltip elements dynamically. The library itself isn’t the problem. Your implementation usually is.
Typical example:
import { useTooltip, TooltipWithBounds } from "@visx/tooltip";
function MyChart() {
const {
tooltipData,
tooltipLeft,
tooltipTop,
tooltipOpen,
showTooltip,
hideTooltip,
} = useTooltip();
return (
<div className="chart-wrap">
<svg width={600} height={300}>
{/* chart content */}
</svg>
{tooltipOpen && (
<TooltipWithBounds
top={tooltipTop}
left={tooltipLeft}
className="chart-tooltip"
>
{tooltipData?.label}
</TooltipWithBounds>
)}
</div>
);
}
That’s generally fine. The catch is whether the tooltip component injects style attributes or whether you pass style objects.
If your CSP blocks styles, switch to class-based styling and test carefully in Chrome, Firefox, and Safari. Safari tends to be the one that exposes style-related assumptions fastest.
Remote chart data: update connect-src
If your Visx chart pulls data from an API, your CSP needs to allow it.
const res = await fetch("https://api.example.com/metrics");
const data = await res.json();
Add the API origin:
connect-src 'self' https://api.example.com;
If you also use WebSockets for live charts:
connect-src 'self' https://api.example.com wss://stream.example.com;
Full example:
Content-Security-Policy:
default-src 'self';
script-src 'self';
style-src 'self';
img-src 'self' data:;
font-src 'self';
connect-src 'self' https://api.example.com wss://stream.example.com;
object-src 'none';
base-uri 'self';
frame-ancestors 'none';
form-action 'self';
If your Visx app includes analytics or consent tooling
This has nothing to do with Visx specifically, but it’s common in production dashboards and marketing-facing data pages.
Here’s a real CSP header used by headertest.com:
content-security-policy: default-src 'self' https://www.googletagmanager.com https://*.cookiebot.com https://*.google-analytics.com; script-src 'self' 'nonce-MmM4OGM2ODktMDQ4OC00NjUxLWFiMTEtODhhNWE0YTNmODk1' '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 solid example of a policy shaped by real integrations, not theory. If your Visx page also includes GA, Tag Manager, or Cookiebot, your policy starts looking more like that and less like the minimal clean version.
CSP for Next.js or Vite apps using Visx
Visx doesn’t usually force CSP changes, but your build tool might during development.
Vite dev server
Vite in dev often needs looser settings than production. Don’t cargo-cult those into prod.
Dev-only example:
Content-Security-Policy:
default-src 'self';
script-src 'self' 'unsafe-eval';
style-src 'self' 'unsafe-inline';
connect-src 'self' ws://localhost:5173 http://localhost:5173;
img-src 'self' data:;
Production should be much tighter:
Content-Security-Policy:
default-src 'self';
script-src 'self';
style-src 'self';
connect-src 'self' https://api.example.com;
img-src 'self' data:;
object-src 'none';
base-uri 'self';
frame-ancestors 'none';
Next.js
If you use inline hydration helpers, analytics bootstraps, or framework-generated inline scripts, you may need nonces or hashes in script-src.
That’s not a Visx requirement. That’s your framework and page setup.
Nonce example:
script-src 'self' 'nonce-rAnd0m123';
Rendered HTML:
<script nonce="rAnd0m123">
window.__CHART_THEME__ = { accent: "#4f46e5" };
</script>
If you go down the nonce route, be consistent across all server-rendered inline scripts.
Common CSP errors with Visx setups
1. Tooltip breaks, chart still renders
Usually style-src is too strict for how the tooltip is being positioned or styled.
Try replacing inline styles with CSS classes first. Only add 'unsafe-inline' for styles if you’ve confirmed you really need it.
2. Chart data never loads
Check connect-src.
Browser console error usually looks like:
Refused to connect to 'https://api.example.com/metrics' because it violates the following Content Security Policy directive: "connect-src 'self'".
Fix:
connect-src 'self' https://api.example.com;
3. Icons or embedded images inside chart annotations don’t render
If you use external image URLs in or around charts:
img-src 'self' data: https:;
If you know the exact host, lock it down harder:
img-src 'self' data: https://cdn.example.com;
4. You copied a CSP that includes unsafe-eval
Don’t keep it unless you can prove it’s required. Visx does not need unsafe-eval.
My recommended production CSP for most Visx apps
If I were shipping a standard React + Visx dashboard today, this is the default I’d start from:
Content-Security-Policy:
default-src 'self';
script-src 'self';
style-src 'self';
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 tooltip or styling approach forces it, relax only this part:
style-src 'self' 'unsafe-inline';
If you have third-party scripts, add them surgically to script-src, connect-src, img-src, and frame-src. Don’t dump everything into default-src and call it a day.
Quick checklist
For Visx, I’d verify these before calling CSP “done”:
- charts render
- tooltips render and position correctly
- API-backed data loads
- exported images or embedded assets display
- no
unsafe-evalin production - no
unsafe-inlinefor scripts style-src 'unsafe-inline'only if you truly need it- all third-party origins explicitly listed
Visx is one of the easier libraries to secure under CSP. That’s a nice change from half the frontend ecosystem. The main rule is simple: blame your app glue code first, not the chart library.