Skip to content

Commit 3012a5b

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 3012a5b

4 files changed

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

117190
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.
118191

119192
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).
120193

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

123231
[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)