diff --git a/examples/styled-jsx-with-csp/.gitignore b/examples/styled-jsx-with-csp/.gitignore new file mode 100644 index 0000000000000..1437c53f70bc2 --- /dev/null +++ b/examples/styled-jsx-with-csp/.gitignore @@ -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 diff --git a/examples/styled-jsx-with-csp/README.md b/examples/styled-jsx-with-csp/README.md new file mode 100644 index 0000000000000..edc94955763f6 --- /dev/null +++ b/examples/styled-jsx-with-csp/README.md @@ -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)). diff --git a/examples/styled-jsx-with-csp/package.json b/examples/styled-jsx-with-csp/package.json new file mode 100644 index 0000000000000..6cc609d415341 --- /dev/null +++ b/examples/styled-jsx-with-csp/package.json @@ -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" + } +} diff --git a/examples/styled-jsx-with-csp/pages/_app.jsx b/examples/styled-jsx-with-csp/pages/_app.jsx new file mode 100644 index 0000000000000..3e51080c46ad9 --- /dev/null +++ b/examples/styled-jsx-with-csp/pages/_app.jsx @@ -0,0 +1,6 @@ +const CustomApp = ({ Component, pageProps }) => + +// Disable static optimization to always server render, making nonce unique on every request +CustomApp.getInitialProps = () => ({}) + +export default CustomApp diff --git a/examples/styled-jsx-with-csp/pages/_document.jsx b/examples/styled-jsx-with-csp/pages/_document.jsx new file mode 100644 index 0000000000000..d56dbb3891489 --- /dev/null +++ b/examples/styled-jsx-with-csp/pages/_document.jsx @@ -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 ( + + + {/* 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 */} + + + +
+ + + + ) + } +} + +export default CustomDocument diff --git a/examples/styled-jsx-with-csp/pages/index.jsx b/examples/styled-jsx-with-csp/pages/index.jsx new file mode 100644 index 0000000000000..706b06db05cc4 --- /dev/null +++ b/examples/styled-jsx-with-csp/pages/index.jsx @@ -0,0 +1,40 @@ +import { useState } from 'react' + +const ClientSideComponent = () => ( + <> + +

This is rendered on client-side

+ +) + +const Home = () => { + const [isVisible, setVisibility] = useState(false) + + const toggleVisibility = () => { + setVisibility((prevState) => !prevState) + } + + return ( + <> + +

Styled-JSX with Content Security Policy

+ + {isVisible ? : null} + + ) +} + +export default Home