Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 52 additions & 0 deletions sections/advanced/security.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,55 @@ Be very careful! This is obviously a made-up example, but CSS injection can be u
have bad repercussions.

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.

### Content Security Policy | v6.4+

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.

There are several ways to configure a nonce, in priority order:

#### `StyleSheetManager` nonce prop (recommended)

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:

```tsx
import { StyleSheetManager } from 'styled-components';

<StyleSheetManager nonce={myNonce}>
<App />
</StyleSheetManager>
```

#### `ServerStyleSheet` constructor

For SSR, pass the nonce when creating the sheet:

```tsx
import { ServerStyleSheet } from 'styled-components';

const sheet = new ServerStyleSheet({ nonce: myNonce });
```

#### Auto-detection via `<meta>` tag

styled-components automatically detects a nonce from these `<meta>` tags in `<head>`:

```html
<!-- Vite convention -->
<meta property="csp-nonce" nonce="abc123">

<!-- styled-components convention -->
<meta name="sc-nonce" content="abc123">
```

This is useful for Vite projects using `html.cspNonce` in `vite.config.js`.

#### Legacy `__webpack_nonce__`

The webpack global `__webpack_nonce__` is still supported as a fallback. Set it before any styled-components code runs:

```js
__webpack_nonce__ = 'abc123';
```

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).
108 changes: 108 additions & 0 deletions sections/advanced/server-side-rendering.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -112,12 +112,120 @@ With styled-components v6.3.0+, RSC is natively supported with zero configuratio

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.

#### Next.js CSP Nonce | v6.4+
If your Next.js app enforces a Content Security Policy, pass the nonce from your middleware to `StyleSheetManager`:

```tsx
// middleware.ts
import { NextResponse } from 'next/server';

export function middleware(request) {
const nonce = crypto.randomUUID();
const response = NextResponse.next();
response.headers.set('x-nonce', nonce);
response.headers.set(
'Content-Security-Policy',
`style-src 'nonce-${nonce}'; script-src 'nonce-${nonce}';`
);
return response;
}
```

```tsx
// app/lib/registry.tsx
'use client';

import { useServerInsertedHTML } from 'next/navigation';
import React, { useState } from 'react';
import { ServerStyleSheet, StyleSheetManager } from 'styled-components';

export default function StyledComponentsRegistry({
children,
nonce,
}: {
children: React.ReactNode;
nonce?: string;
}) {
const [sheet] = useState(() => new ServerStyleSheet({ nonce }));

useServerInsertedHTML(() => {
const styles = sheet.getStyleElement();
sheet.instance.clearTag();
return <>{styles}</>;
});

if (typeof window !== 'undefined') return <>{children}</>;

return (
<StyleSheetManager sheet={sheet.instance} nonce={nonce}>
{children}
</StyleSheetManager>
);
}
```

```tsx
// app/layout.tsx
import { headers } from 'next/headers';
import StyledComponentsRegistry from './lib/registry';

export default async function RootLayout({ children }) {
const nonce = (await headers()).get('x-nonce') ?? undefined;
return (
<html>
<body>
<StyledComponentsRegistry nonce={nonce}>
{children}
</StyledComponentsRegistry>
</body>
</html>
);
}
```

> Note: Using a CSP nonce requires dynamic rendering. Static generation and ISR are not compatible with per-request nonces.

#### Pages Router

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.

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

### Remix / React Router v7

#### Remix CSP Nonce | v6.4+
In Remix, generate a nonce in your server middleware and pass it to `StyleSheetManager` via `entry.server.tsx`:

```tsx
// entry.server.tsx
import { renderToPipeableStream } from 'react-dom/server';
import { ServerStyleSheet, StyleSheetManager } from 'styled-components';
import { ServerRouter } from 'react-router';

export default function handleRequest(request, responseStatusCode, responseHeaders, routerContext) {
const nonce = responseHeaders.get('x-nonce'); // set by your middleware
const sheet = new ServerStyleSheet({ nonce });

const { pipe } = renderToPipeableStream(
sheet.collectStyles(
<ServerRouter context={routerContext} url={request.url} nonce={nonce} />
),
{
nonce,
onShellReady() {
responseHeaders.set('Content-Type', 'text/html');
pipe(response);
},
onAllReady() {
sheet.seal();
},
}
);
}
```

> Keep the nonce server-only. Do not pass it through loaders or expose it to the client—this defeats the CSP security model.

### Gatsby

[Gatsby](https://www.gatsbyjs.com/) has an official plugin that enables server-side rendering for styled-components.
Expand Down
9 changes: 9 additions & 0 deletions sections/api/helpers/style-sheet-manager.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,15 @@ A helper component for modifying how your styles are processed. For a given subt
</Column>
</Row>

<Row>
<Column>
<Code>nonce (v6.4+)</Code>
</Column>
<Column>
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.
</Column>
</Row>

<Row>
<Column>
<Code>target</Code>
Expand Down
33 changes: 33 additions & 0 deletions sections/tooling/vite.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -86,3 +86,36 @@ export function render() {
```

If you use [Vike](https://vike.dev/) (formerly vite-plugin-ssr), the `vike-react-styled-components` extension handles style collection automatically.

### CSP Nonce with Vite | v6.4+

Vite has built-in CSP nonce support via `html.cspNonce`. styled-components auto-detects the nonce from Vite's `<meta property="csp-nonce">` tag:

```ts
// vite.config.ts
export default defineConfig({
html: {
cspNonce: 'NONCE_PLACEHOLDER',
},
});
```

Your server must replace `NONCE_PLACEHOLDER` with a per-request random value. For example, with an Express middleware:

```ts
app.use((req, res, next) => {
res.locals.nonce = crypto.randomUUID();
next();
});

app.get('*', (req, res) => {
let html = getViteHtml();
html = html.replaceAll('NONCE_PLACEHOLDER', res.locals.nonce);
res.setHeader('Content-Security-Policy', `style-src 'nonce-${res.locals.nonce}';`);
res.send(html);
});
```

styled-components reads the nonce at runtime from the `<meta>` tag's `.nonce` DOM property. No additional configuration is needed.

> 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.
Loading