CSP for SolidJS vs SolidStart: Pros, Cons, and Setup
Table of Contents
SolidJS and SolidStart sit close together, but CSP feels very different depending on which one you’re shipping.
If you’re building a plain SolidJS SPA with Vite, CSP is mostly about locking down your static assets, avoiding inline script mistakes, and dealing with third-party analytics. If you’re building with SolidStart, CSP becomes a runtime concern too: SSR, streaming, nonce propagation, server headers, and hydration all enter the picture.
That difference matters a lot. I’ve seen teams assume “we added a CSP header” and then discover the app only works because unsafe-inline quietly stayed enabled. That’s not a serious policy. That’s a polite suggestion.
The short version
SolidJS is simpler to secure with CSP if your app is mostly client-rendered and static.
SolidStart gives you more control and better security patterns for nonce-based CSP, but it’s also easier to misconfigure because the framework is doing more on the server.
If you want the cleanest path to a strict CSP, I’d usually pick:
- SolidJS for static/SPA deployments with minimal dynamic server behavior
- SolidStart for SSR apps where you want per-request nonces and tighter script control
What CSP is doing here
CSP reduces the blast radius of XSS by telling the browser what it may load and execute. The directives that matter most for Solid apps are usually:
default-srcscript-srcstyle-srcconnect-srcimg-srcfont-srcframe-srcbase-uriform-actionobject-srcframe-ancestors
A real production-style example looks like this 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-ODBjN2VkYWQtZjIzZC00NjExLWJmNDYtMDRlNGRmYWUwZjBk' '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 very normal modern policy: nonce-based scripts, strict dynamic loading, third-party analytics allowances, and object-src 'none'.
If you want ready-made policy patterns, https://csp-examples.com is handy.
CSP with SolidJS
By “SolidJS” here, I mean the client-side app setup most people deploy with Vite.
Why SolidJS is easier
A plain SolidJS app is usually:
- static HTML shell
- bundled JS files
- predictable asset paths
- no server-side HTML generation per request
That means your CSP can often be static too.
Example baseline policy for a self-hosted SolidJS SPA:
Content-Security-Policy:
default-src 'self';
script-src 'self';
style-src 'self';
img-src 'self' data:;
font-src 'self';
connect-src 'self';
base-uri 'self';
form-action 'self';
frame-ancestors 'none';
object-src 'none';
That’s clean and strong. No inline scripts, no broad wildcards, no drama.
Pros of CSP in SolidJS
1. Static policy is usually enough
You can often configure CSP at the CDN, reverse proxy, or static host and forget about it.
2. Less nonce plumbing
You usually don’t need to generate a nonce per request unless you’re injecting inline scripts yourself.
3. Easier debugging
When a script is blocked, the source is usually obvious: a third-party tag, an inline snippet, or a dev-only behavior that leaked into production.
4. Strong fit for hash-based or no-inline setups
If your app ships only external JS/CSS, script-src 'self' works very well.
Cons of CSP in SolidJS
1. Third-party scripts ruin the simplicity fast
The second marketing drops in GTM, Cookiebot, or analytics, your tidy policy starts expanding.
Using the headertest-style pattern, your script-src may end up looking more like:
script-src 'self' 'nonce-<generated-nonce>' 'strict-dynamic' https://www.googletagmanager.com https://*.cookiebot.com https://*.google-analytics.com;
That’s not SolidJS’s fault, but it’s where most “simple SPA CSP” plans go sideways.
2. Vite dev mode is not representative
Development often needs looser settings for HMR, eval-like behavior, websocket connections, and inline helpers. A production CSP that’s solid may break dev instantly.
I usually treat dev and prod as separate CSP worlds.
3. Inline bootstrapping can sneak in
If you manually inject config into index.html, you may force yourself into nonces or hashes:
<script>
window.APP_CONFIG = { apiBase: "/api" };
</script>
That one innocent block changes your CSP design.
CSP with SolidStart
SolidStart is different because the server participates in rendering. That makes CSP both more powerful and more annoying.
Why SolidStart is more capable
With SSR, you can generate a fresh nonce on every request and attach it to:
- the CSP header
- framework-generated script tags
- your own inline scripts
- preload or hydration-related markup where needed
That’s exactly how strict CSP is supposed to work.
Pros of CSP in SolidStart
1. Per-request nonce support fits SSR naturally
This is the biggest win. You can do real nonce-based CSP instead of compromising with unsafe-inline.
Conceptually, the server flow looks like this:
import { randomUUID } from "node:crypto";
export function createCspNonce() {
return randomUUID();
}
export function buildCsp(nonce: string) {
return [
"default-src 'self'",
`script-src 'self' 'nonce-${nonce}' 'strict-dynamic'`,
"style-src 'self'",
"img-src 'self' data: https:",
"font-src 'self'",
"connect-src 'self'",
"base-uri 'self'",
"form-action 'self'",
"frame-ancestors 'none'",
"object-src 'none'"
].join("; ");
}
Then you attach the header in your request handler and pass the nonce into rendering.
2. Better path to strict CSP
Because the framework controls SSR output, you have a realistic chance of making all executable scripts nonce-approved.
3. Easier support for mixed dynamic requirements
Need analytics only on some routes? Need a consent manager? Need route-specific embeds? Server-side policy generation makes that manageable.
Cons of CSP in SolidStart
1. Nonce propagation is easy to get wrong
This is the most common failure mode. You generate a nonce but forget to apply it everywhere framework or custom scripts are emitted.
Then hydration fails, and the app looks half-broken in a way that wastes an afternoon.
2. Streaming SSR complicates assumptions
If you stream HTML, script ordering and injected chunks matter. A CSP that looks correct on paper can still fail if the nonce is missing from later script tags.
3. Hosting adapters differ
Node, serverless, edge, and custom adapters don’t all expose headers the same way. Your CSP implementation may be clean on one target and awkward on another.
4. Third-party integrations are still the hard part
SSR doesn’t save you from marketing scripts. It just gives you better tools to control them.
Example comparison
Here’s how I think about the two in practice.
SolidJS: best-case CSP profile
Good fit if you have:
- static deployment
- no inline scripts
- no SSR
- limited third-party code
- API calls only to a known backend
Example:
Content-Security-Policy:
default-src 'self';
script-src 'self';
style-src 'self';
img-src 'self' data:;
connect-src 'self' https://api.example.com;
font-src 'self';
base-uri 'self';
form-action 'self';
frame-ancestors 'none';
object-src 'none';
That’s easy to reason about and easy to keep strict.
SolidStart: best-case CSP profile
Good fit if you have:
- SSR or hybrid rendering
- need for per-request nonces
- some unavoidable inline framework bootstrapping
- route-aware security needs
- tighter control over dynamic script execution
Example pattern:
Content-Security-Policy:
default-src 'self';
script-src 'self' 'nonce-<nonce>' 'strict-dynamic';
style-src 'self';
img-src 'self' data: https:;
connect-src 'self' https://api.example.com;
font-src 'self';
base-uri 'self';
form-action 'self';
frame-ancestors 'none';
object-src 'none';
That’s stronger than falling back to unsafe-inline, assuming your app actually applies the nonce consistently.
My opinionated take
If your Solid app can be a static SPA, CSP is less work in plain SolidJS. Fewer moving parts, fewer surprises.
If you need SSR, I’d rather do CSP in SolidStart than try to fake strict CSP around a server-rendered app without proper nonce handling. SolidStart gives you the right security model, even if setup is fussier.
The trap is thinking SolidStart automatically means secure CSP. It doesn’t. It just means strict CSP is possible.
Practical recommendations
Choose SolidJS if:
- you want the easiest CSP rollout
- your app is mostly static and client-rendered
- you can avoid inline code
- you want a stable, mostly static header at the edge
Choose SolidStart if:
- you need SSR or streaming
- you want nonce-based
script-src - you need per-request policy generation
- you’re willing to test hydration and inline behavior carefully
What I’d avoid in both
script-src 'unsafe-inline'default-src *- broad
https:allowances for scripts - copying a giant CSP from another app without trimming it
- mixing nonce, hash, and permissive fallbacks without understanding why
And for styles: if you still need style-src 'unsafe-inline', treat it as technical debt. Sometimes it’s temporarily necessary, but I wouldn’t call it done.
Final scorecard
SolidJS
Pros
- simpler static CSP
- less server coordination
- easier deployment at CDN/proxy layer
- strong fit for no-inline architectures
Cons
- dev/prod CSP mismatch
- third-party scripts quickly bloat policy
- inline config snippets force exceptions
- less natural support for per-request nonce patterns
SolidStart
Pros
- natural fit for nonce-based strict CSP
- better for SSR and dynamic rendering
- route-aware and request-aware policies
- stronger long-term security posture for complex apps
Cons
- more implementation complexity
- nonce propagation bugs are common
- streaming and hydration need testing
- adapter/runtime differences add friction
If I were building a security-sensitive app and already needed SSR, I’d take SolidStart and do nonce-based CSP properly. If I were building a dashboard SPA with minimal third-party baggage, I’d pick SolidJS and keep the policy brutally simple.
For framework-specific behavior and rendering details, the official docs are the place to check before wiring headers into production: SolidJS documentation and SolidStart documentation.