Remix Icon is easy to drop into a project, which is exactly why people ship it with a sloppy CSP.
I’ve seen this a lot: someone adds the Remix Icon CDN snippet, the icons don’t render, they get a wall of CSP errors, and the “fix” becomes style-src 'unsafe-inline' plus a couple of random hostnames copied from the console. That works, but it’s the kind of fix that quietly makes the policy worse every time the app changes.
If you’re using Remix Icons, the CSP surface area is pretty small. You usually need to think about:
- where the CSS comes from
- where the font files come from
- whether you self-host or use a CDN
- whether your framework injects styles in a way CSP blocks
That’s it.
How Remix Icons are typically loaded
Most setups use one of these approaches:
1. Self-hosted CSS and fonts
You copy the Remix Icon CSS and font files into your app and serve them yourself:
<link rel="stylesheet" href="/assets/remixicon/remixicon.css">
<i class="ri-home-line"></i>
The CSS then references font files like:
@font-face {
font-family: "remixicon";
src: url("./fonts/remixicon.woff2?t=1718033351984") format("woff2"),
url("./fonts/remixicon.woff?t=1718033351984") format("woff");
}
This is the cleanest CSP setup.
2. CDN-hosted CSS and fonts
You load the stylesheet from a CDN, and that stylesheet loads font files from the same or another CDN:
<link
rel="stylesheet"
href="https://cdn.example.com/remixicon/remixicon.css"
>
This needs explicit style-src and font-src allowances.
The safest option: self-host Remix Icons
If you can self-host, do it. Your CSP stays simpler and tighter.
A minimal policy for self-hosted Remix Icons might look like this:
Content-Security-Policy:
default-src 'self';
style-src 'self';
font-src 'self';
img-src 'self' data:;
script-src 'self';
object-src 'none';
base-uri 'self';
frame-ancestors 'none';
That works because:
- the stylesheet is loaded from your own origin
- the font files are loaded from your own origin
- no inline styles are required
- no extra domains are needed
If your HTML looks like this:
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Remix Icons Demo</title>
<link rel="stylesheet" href="/assets/remixicon/remixicon.css">
</head>
<body>
<button class="icon-button">
<i class="ri-search-line" aria-hidden="true"></i>
Search
</button>
</body>
</html>
…and your assets are actually served from /assets/remixicon/, the policy is enough.
Express example with self-hosted Remix Icons
Here’s a simple Express app that serves self-hosted Remix Icons with a CSP header:
import express from "express";
import path from "path";
const app = express();
app.use("/assets", express.static(path.join(process.cwd(), "public/assets")));
app.use((req, res, next) => {
res.setHeader(
"Content-Security-Policy",
[
"default-src 'self'",
"script-src 'self'",
"style-src 'self'",
"font-src 'self'",
"img-src 'self' data:",
"object-src 'none'",
"base-uri 'self'",
"frame-ancestors 'none'"
].join("; ")
);
next();
});
app.get("/", (req, res) => {
res.send(`
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Remix Icons CSP Demo</title>
<link rel="stylesheet" href="/assets/remixicon/remixicon.css">
</head>
<body>
<h1><i class="ri-shield-check-line"></i> CSP Demo</h1>
</body>
</html>
`);
});
app.listen(3000, () => {
console.log("Listening on http://localhost:3000");
});
That’s the baseline I’d start from.
CDN setup: what CSP directives you actually need
If you load Remix Icons from a CDN, you need to allow the stylesheet origin in style-src and the font origin in font-src.
Example:
Content-Security-Policy:
default-src 'self';
script-src 'self';
style-src 'self' https://cdn.example.com;
font-src 'self' https://cdn.example.com;
img-src 'self' data:;
object-src 'none';
base-uri 'self';
frame-ancestors 'none';
And your HTML:
<link
rel="stylesheet"
href="https://cdn.example.com/remixicon/remixicon.css"
>
If the CSS is on one host and font files are fetched from another, both must be allowed. This is where people get tripped up. The browser fetches the CSS first, then separately fetches the font URLs inside it. A style-src allowlist does not automatically cover font requests.
Don’t cargo-cult default-src
A lot of teams assume default-src handles everything. It doesn’t once you define more specific directives.
For example:
Content-Security-Policy:
default-src 'self' https://cdn.example.com;
style-src 'self';
font-src 'self';
This blocks Remix Icon assets from cdn.example.com for styles and fonts, because style-src and font-src override default-src.
If you need a refresher on directive fallback behavior, https://csp-guide.com is a good reference.
Real-world header analysis
Here’s a real CSP header from headertest.com:
content-security-policy: default-src 'self' https://www.googletagmanager.com https://*.cookiebot.com https://*.google-analytics.com; script-src 'self' 'nonce-NzA4ZThkZWItMmY4ZC00M2M0LWJjYjEtOTNjYTlhNmRjNTli' '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'
If you wanted to add self-hosted Remix Icons to that site, you probably wouldn’t need to change anything at all, because:
style-src 'self'already allows your own hosted CSSfont-src 'self'already allows your own hosted font files
If you wanted to add CDN-hosted Remix Icons, you’d need to update both directives. For example:
content-security-policy:
default-src 'self' https://www.googletagmanager.com https://*.cookiebot.com https://*.google-analytics.com;
script-src 'self' 'nonce-NzA4ZThkZWItMmY4ZC00M2M0LWJjYjEtOTNjYTlhNmRjNTli' '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 https://cdn.example.com;
img-src 'self' data: https:;
font-src 'self' https://cdn.example.com;
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 the exact kind of targeted change you want. No random broadening of default-src, no font-src https: nonsense.
Common CSP errors with Remix Icons
Error: stylesheet blocked
Browser console usually says something like:
Refused to load the stylesheet 'https://cdn.example.com/remixicon.css'
because it violates the following Content Security Policy directive:
"style-src 'self'".
Fix:
style-src 'self' https://cdn.example.com;
Error: font blocked
You’ll see something like:
Refused to load the font 'https://cdn.example.com/fonts/remixicon.woff2'
because it violates the following Content Security Policy directive:
"font-src 'self'".
Fix:
font-src 'self' https://cdn.example.com;
Error: inline styles blocked
Remix Icons themselves usually don’t require inline styles. If your framework or component library injects style attributes or <style> blocks, that’s a separate problem.
A lot of people solve this with:
style-src 'self' 'unsafe-inline';
I avoid that unless I absolutely have to. If your app generates inline <style> tags, a nonce or hash-based approach is usually better. The official CSP docs are here: https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP
Nginx example
If you’re serving a static app with Nginx and self-hosted Remix Icons:
server {
listen 443 ssl;
server_name example.com;
root /var/www/example;
index index.html;
add_header Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self'; font-src 'self'; img-src 'self' data:; object-src 'none'; base-uri 'self'; frame-ancestors 'none'" always;
location / {
try_files $uri $uri/ /index.html;
}
}
For a CDN-hosted setup:
add_header Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self' https://cdn.example.com; font-src 'self' https://cdn.example.com; img-src 'self' data:; object-src 'none'; base-uri 'self'; frame-ancestors 'none'" always;
How I’d choose between font icons and SVG under CSP
This is the part where I get opinionated.
If I’m already using Remix Icons and don’t want churn, self-hosted font files are fine. CSP-wise, they’re easy.
If I’m starting fresh, I usually prefer SVG icons. They avoid font-src entirely, behave more predictably, and don’t have some of the old icon-font quirks around accessibility and rendering. But if your project already uses Remix Icon as a font-based package, there’s no security reason to rip it out. Just self-host it and write a tight policy.
A practical checklist
When Remix Icons break under CSP, I check these in order:
- Is the stylesheet self-hosted or CDN-hosted?
- Does
style-srcallow that exact origin? - Where do the font files load from?
- Does
font-srcallow that exact origin? - Are there redirects to a different hostname?
- Is the app using inline styles for unrelated reasons?
- Am I accidentally relying on
default-srcwhenstyle-srcorfont-srcis already set?
That usually finds the issue in a minute or two.
Recommended policies
Best case: self-hosted Remix Icons
Content-Security-Policy:
default-src 'self';
script-src 'self';
style-src 'self';
font-src 'self';
img-src 'self' data:;
object-src 'none';
base-uri 'self';
frame-ancestors 'none';
Acceptable case: CDN-hosted Remix Icons
Content-Security-Policy:
default-src 'self';
script-src 'self';
style-src 'self' https://cdn.example.com;
font-src 'self' https://cdn.example.com;
img-src 'self' data:;
object-src 'none';
base-uri 'self';
frame-ancestors 'none';
That’s really the whole game for Remix Icons under CSP: keep the asset origins explicit, self-host if you can, and don’t “fix” icon loading by weakening the rest of the policy.