Skip to content

Commit

Permalink
Add an example for styled-jsx with content security policy (#23021)
Browse files Browse the repository at this point in the history
This is an example on how to use styled-jsx with content security policy.
  • Loading branch information
KarthikeyanRanasthala authored Mar 12, 2021
1 parent 0ec58c6 commit c1b2b3f
Show file tree
Hide file tree
Showing 6 changed files with 172 additions and 0 deletions.
34 changes: 34 additions & 0 deletions examples/styled-jsx-with-csp/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.

# dependencies
/node_modules
/.pnp
.pnp.js

# testing
/coverage

# next.js
/.next/
/out/

# production
/build

# misc
.DS_Store
*.pem

# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*

# local env files
.env.local
.env.development.local
.env.test.local
.env.production.local

# vercel
.vercel
27 changes: 27 additions & 0 deletions examples/styled-jsx-with-csp/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# Styled-JSX with Content Security Policy

This example showcases how you can use `nonce` for `style-src` directive in `Content Security Policy` with `styled-jsx`.

Checkout the [demo](https://styled-jsx-with-csp.vercel.app/) and notice the following,

- `style-src` directive in `Content-Security-Policy` response header.
- `meta` tag to pass on the `nonce` to styled-jsx for client-side rendering.
- `style` tags with `nonce` attributes.

## Deploy your own

Deploy the example using [Vercel](https://vercel.com?utm_source=github&utm_medium=readme&utm_campaign=next-example):

[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/git/external?repository-url=https://github.com/vercel/next.js/tree/canary/examples/styled-jsx-with-csp&project-name=styled-jsx-with-csp&repository-name=styled-jsx-with-csp)

## How to use

Execute [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app) with [npm](https://docs.npmjs.com/cli/init) or [Yarn](https://yarnpkg.com/lang/en/docs/cli/create/) to bootstrap the example:

```bash
npx create-next-app --example styled-jsx-with-csp styled-jsx-with-csp-app
# or
yarn create next-app --example styled-jsx-with-csp styled-jsx-with-csp-app
```

Deploy it to the cloud with [Vercel](https://vercel.com/new?utm_source=github&utm_medium=readme&utm_campaign=next-example) ([Documentation](https://nextjs.org/docs/deployment)).
16 changes: 16 additions & 0 deletions examples/styled-jsx-with-csp/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"name": "styled-jsx-with-csp",
"version": "1.0.0",
"private": true,
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start"
},
"dependencies": {
"nanoid": "3.1.21",
"next": "10.0.8",
"react": "17.0.1",
"react-dom": "17.0.1"
}
}
6 changes: 6 additions & 0 deletions examples/styled-jsx-with-csp/pages/_app.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
const CustomApp = ({ Component, pageProps }) => <Component {...pageProps} />

// Disable static optimization to always server render, making nonce unique on every request
CustomApp.getInitialProps = () => ({})

export default CustomApp
49 changes: 49 additions & 0 deletions examples/styled-jsx-with-csp/pages/_document.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import Document, { Html, Head, Main, NextScript } from 'next/document'
import flush from 'styled-jsx/server'

import { nanoid } from 'nanoid'

class CustomDocument extends Document {
static async getInitialProps(ctx) {
const nonce = nanoid()

// https://github.com/vercel/next.js/blob/canary/packages/next/pages/_document.tsx#L89
const { html, head } = await ctx.renderPage()

// Adds `nonce` to style tags on Server Side Rendering
const styles = [...flush({ nonce })]

let contentSecurityPolicy = ''
if (process.env.NODE_ENV === 'production') {
contentSecurityPolicy = `default-src 'self'; style-src 'nonce-${nonce}';`
} else {
// react-refresh needs 'unsafe-eval'
// Next.js needs 'unsafe-inline' during development https://github.com/vercel/next.js/blob/canary/packages/next/client/dev/fouc.js
// Specifying 'nonce' makes a modern browsers ignore 'unsafe-inline'
contentSecurityPolicy = `default-src 'self'; style-src 'unsafe-inline'; script-src 'self' 'unsafe-eval';`
}

ctx.res.setHeader('Content-Security-Policy', contentSecurityPolicy)

return { styles, html, head, nonce }
}

render() {
return (
<Html>
<Head>
{/* Styled-JSX will add this `nonce` to style tags on Client Side Rendering */}
{/* https://github.com/vercel/styled-jsx/blob/master/src/lib/stylesheet.js#L31 */}
{/* https://github.com/vercel/styled-jsx/blob/master/src/lib/stylesheet.js#L240 */}
<meta property="csp-nonce" content={this.props.nonce} />
</Head>
<body>
<Main />
<NextScript />
</body>
</Html>
)
}
}

export default CustomDocument
40 changes: 40 additions & 0 deletions examples/styled-jsx-with-csp/pages/index.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { useState } from 'react'

const ClientSideComponent = () => (
<>
<style jsx>
{`
.title {
font-size: 24px;
color: green;
}
`}
</style>
<p className="title">This is rendered on client-side</p>
</>
)

const Home = () => {
const [isVisible, setVisibility] = useState(false)

const toggleVisibility = () => {
setVisibility((prevState) => !prevState)
}

return (
<>
<style jsx>
{`
.title {
font-size: 24px;
}
`}
</style>
<p className="title">Styled-JSX with Content Security Policy</p>
<button onClick={toggleVisibility}>Toggle</button>
{isVisible ? <ClientSideComponent /> : null}
</>
)
}

export default Home

0 comments on commit c1b2b3f

Please sign in to comment.