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-src
  • script-src
  • style-src
  • connect-src
  • img-src
  • font-src
  • frame-src
  • base-uri
  • form-action
  • object-src
  • frame-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.