Skip to content

Commit 8bb726a

Browse files
committed
docs: add CSP nonce documentation
- Add Content Security Policy section to security page with all nonce configuration methods (StyleSheetManager prop, ServerStyleSheet option, meta tag auto-detection, __webpack_nonce__) - Add Next.js App Router CSP nonce guide with middleware + registry example - Add Remix/React Router v7 CSP nonce guide with entry.server.tsx example - Add Vite CSP nonce guide using html.cspNonce auto-detection - Add nonce prop to StyleSheetManager API reference Companion to styled-components/styled-components#5693
1 parent 319ea09 commit 8bb726a

4 files changed

Lines changed: 204 additions & 0 deletions

File tree

sections/advanced/security.mdx

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,3 +21,55 @@ Be very careful! This is obviously a made-up example, but CSS injection can be u
2121
have bad repercussions.
2222

2323
You can use [`CSS.escape`](https://developer.mozilla.org/en-US/docs/Web/API/CSS/escape) to sanitize CSS from JavaScript. It is well-supported in all modern browsers.
24+
25+
### Content Security Policy | v6.4+
26+
27+
If your app uses a [Content Security Policy](https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP) that restricts inline styles, you need to provide a nonce so the browser allows styled-components' injected `<style>` tags.
28+
29+
There are several ways to configure a nonce, in priority order:
30+
31+
#### `StyleSheetManager` nonce prop (recommended)
32+
33+
Pass the nonce explicitly via React context. This is the recommended approach for Next.js, Remix, and any framework where the nonce is available server-side:
34+
35+
```tsx
36+
import { StyleSheetManager } from 'styled-components';
37+
38+
<StyleSheetManager nonce={myNonce}>
39+
<App />
40+
</StyleSheetManager>
41+
```
42+
43+
#### `ServerStyleSheet` constructor
44+
45+
For SSR, pass the nonce when creating the sheet:
46+
47+
```tsx
48+
import { ServerStyleSheet } from 'styled-components';
49+
50+
const sheet = new ServerStyleSheet({ nonce: myNonce });
51+
```
52+
53+
#### Auto-detection via `<meta>` tag
54+
55+
styled-components automatically detects a nonce from these `<meta>` tags in `<head>`:
56+
57+
```html
58+
<!-- Vite convention -->
59+
<meta property="csp-nonce" nonce="abc123">
60+
61+
<!-- styled-components convention -->
62+
<meta name="sc-nonce" content="abc123">
63+
```
64+
65+
This is useful for Vite projects using `html.cspNonce` in `vite.config.js`.
66+
67+
#### Legacy `__webpack_nonce__`
68+
69+
The webpack global `__webpack_nonce__` is still supported as a fallback. Set it before any styled-components code runs:
70+
71+
```js
72+
__webpack_nonce__ = 'abc123';
73+
```
74+
75+
For framework-specific setup, see the CSP guides for [Next.js](/docs/advanced#nextjs-csp), [Vite](/docs/tooling#vite), and [Remix](/docs/advanced#remix-csp).

sections/advanced/server-side-rendering.mdx

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,12 +112,122 @@ With styled-components v6.3.0+, RSC is natively supported with zero configuratio
112112

113113
With styled-components v6.0-6.2, you need to put a styled-components registry in one of your layout files, as [described in Next.js docs](https://nextjs.org/docs/app/building-your-application/styling/css-in-js#styled-components). The `'use client'` directive is required, so styled-components will appear in your client bundle.
114114

115+
#### Next.js CSP Nonce | v6.4+ {#nextjs-csp}
116+
117+
If your Next.js app enforces a Content Security Policy, pass the nonce from your middleware to `StyleSheetManager`:
118+
119+
```tsx
120+
// middleware.ts
121+
import { NextResponse } from 'next/server';
122+
123+
export function middleware(request) {
124+
const nonce = crypto.randomUUID();
125+
const response = NextResponse.next();
126+
response.headers.set('x-nonce', nonce);
127+
response.headers.set(
128+
'Content-Security-Policy',
129+
`style-src 'nonce-${nonce}'; script-src 'nonce-${nonce}';`
130+
);
131+
return response;
132+
}
133+
```
134+
135+
```tsx
136+
// app/lib/registry.tsx
137+
'use client';
138+
139+
import { useServerInsertedHTML } from 'next/navigation';
140+
import React, { useState } from 'react';
141+
import { ServerStyleSheet, StyleSheetManager } from 'styled-components';
142+
143+
export default function StyledComponentsRegistry({
144+
children,
145+
nonce,
146+
}: {
147+
children: React.ReactNode;
148+
nonce?: string;
149+
}) {
150+
const [sheet] = useState(() => new ServerStyleSheet({ nonce }));
151+
152+
useServerInsertedHTML(() => {
153+
const styles = sheet.getStyleElement();
154+
sheet.instance.clearTag();
155+
return <>{styles}</>;
156+
});
157+
158+
if (typeof window !== 'undefined') return <>{children}</>;
159+
160+
return (
161+
<StyleSheetManager sheet={sheet.instance} nonce={nonce}>
162+
{children}
163+
</StyleSheetManager>
164+
);
165+
}
166+
```
167+
168+
```tsx
169+
// app/layout.tsx
170+
import { headers } from 'next/headers';
171+
import StyledComponentsRegistry from './lib/registry';
172+
173+
export default async function RootLayout({ children }) {
174+
const nonce = (await headers()).get('x-nonce') ?? undefined;
175+
return (
176+
<html>
177+
<body>
178+
<StyledComponentsRegistry nonce={nonce}>
179+
{children}
180+
</StyledComponentsRegistry>
181+
</body>
182+
</html>
183+
);
184+
}
185+
```
186+
187+
> Note: Using a CSP nonce requires dynamic rendering. Static generation and ISR are not compatible with per-request nonces.
188+
115189
#### Pages Router
116190

117191
Add `styledComponents: true` to the compiler options in `next.config.js`, then modify `_document` with `getInitialProps` to support SSR. See the [Next.js styled-components example](https://github.com/vercel/next.js/tree/canary/examples/with-styled-components) for reference.
118192

119193
If you're using Babel instead of SWC, refer to the [Babel-based example](https://github.com/vercel/next.js/tree/canary/examples/with-styled-components-babel).
120194

195+
### Remix / React Router v7
196+
197+
#### Remix CSP Nonce | v6.4+ {#remix-csp}
198+
199+
In Remix, generate a nonce in your server middleware and pass it to `StyleSheetManager` via `entry.server.tsx`:
200+
201+
```tsx
202+
// entry.server.tsx
203+
import { renderToPipeableStream } from 'react-dom/server';
204+
import { ServerStyleSheet, StyleSheetManager } from 'styled-components';
205+
import { ServerRouter } from 'react-router';
206+
207+
export default function handleRequest(request, responseStatusCode, responseHeaders, routerContext) {
208+
const nonce = responseHeaders.get('x-nonce'); // set by your middleware
209+
const sheet = new ServerStyleSheet({ nonce });
210+
211+
const { pipe } = renderToPipeableStream(
212+
sheet.collectStyles(
213+
<ServerRouter context={routerContext} url={request.url} nonce={nonce} />
214+
),
215+
{
216+
nonce,
217+
onShellReady() {
218+
responseHeaders.set('Content-Type', 'text/html');
219+
pipe(response);
220+
},
221+
onAllReady() {
222+
sheet.seal();
223+
},
224+
}
225+
);
226+
}
227+
```
228+
229+
> Keep the nonce server-only. Do not pass it through loaders or expose it to the client—this defeats the CSP security model.
230+
121231
### Gatsby
122232

123233
[Gatsby](https://www.gatsbyjs.com/) has an official plugin that enables server-side rendering for styled-components.

sections/api/helpers/style-sheet-manager.mdx

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,15 @@ A helper component for modifying how your styles are processed. For a given subt
5353
</Column>
5454
</Row>
5555

56+
<Row>
57+
<Column>
58+
<Code>nonce (v6.4+)</Code>
59+
</Column>
60+
<Column>
61+
CSP nonce to attach to injected <code>&lt;style&gt;</code> tags. Overrides auto-detection from <code>&lt;meta&gt;</code> tags or <code>__webpack_nonce__</code>. See the <a href="/docs/advanced#content-security-policy">CSP section</a> for details.
62+
</Column>
63+
</Row>
64+
5665
<Row>
5766
<Column>
5867
<Code>target</Code>

sections/tooling/vite.mdx

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,3 +86,36 @@ export function render() {
8686
```
8787

8888
If you use [Vike](https://vike.dev/) (formerly vite-plugin-ssr), the `vike-react-styled-components` extension handles style collection automatically.
89+
90+
### CSP Nonce with Vite | v6.4+
91+
92+
Vite has built-in CSP nonce support via `html.cspNonce`. styled-components auto-detects the nonce from Vite's `<meta property="csp-nonce">` tag:
93+
94+
```ts
95+
// vite.config.ts
96+
export default defineConfig({
97+
html: {
98+
cspNonce: 'NONCE_PLACEHOLDER',
99+
},
100+
});
101+
```
102+
103+
Your server must replace `NONCE_PLACEHOLDER` with a per-request random value. For example, with an Express middleware:
104+
105+
```ts
106+
app.use((req, res, next) => {
107+
res.locals.nonce = crypto.randomUUID();
108+
next();
109+
});
110+
111+
app.get('*', (req, res) => {
112+
let html = getViteHtml();
113+
html = html.replaceAll('NONCE_PLACEHOLDER', res.locals.nonce);
114+
res.setHeader('Content-Security-Policy', `style-src 'nonce-${res.locals.nonce}';`);
115+
res.send(html);
116+
});
117+
```
118+
119+
styled-components reads the nonce at runtime from the `<meta>` tag's `.nonce` DOM property. No additional configuration is needed.
120+
121+
> In pure SPA deployments without server-side nonce replacement, the nonce is static and does not provide security benefits. CSP nonces are only effective when generated per-request.

0 commit comments

Comments
 (0)