diff --git a/docs/basic-features/eslint.md b/docs/basic-features/eslint.md index a8c99aea6e3ba..b85d16f44cacb 100644 --- a/docs/basic-features/eslint.md +++ b/docs/basic-features/eslint.md @@ -98,6 +98,7 @@ Next.js provides an ESLint plugin, [`eslint-plugin-next`](https://www.npmjs.com/ | ✔️ | [next/inline-script-id](/docs/messages/inline-script-id.md) | Enforce id attribute on next/script components with inline content | | ✔️ | next/no-typos | Ensure no typos were made declaring [Next.js's data fetching function](/docs/basic-features/data-fetching/overview.md) | | ✔️ | [next/next-script-for-ga](/docs/messages/next-script-for-ga.md) | Use the Script component to defer loading of the script until necessary. | +| ✔️ | [next/no-styled-jsx-in-document](/docs/messages/no-styled-jsx-in-document.md) | styled-jsx should not be used in \_document | - ✔: Enabled in the recommended configuration diff --git a/errors/manifest.json b/errors/manifest.json index fe726f264ff89..79adcf8104af1 100644 --- a/errors/manifest.json +++ b/errors/manifest.json @@ -398,6 +398,10 @@ "title": "no-server-import-in-page", "path": "/errors/no-server-import-in-page.md" }, + { + "title": "no-styled-jsx-in-document", + "path": "/errors/no-styled-jsx-in-document.md" + }, { "title": "no-sync-scripts", "path": "/errors/no-sync-scripts.md" diff --git a/errors/no-styled-jsx-in-document.md b/errors/no-styled-jsx-in-document.md new file mode 100644 index 0000000000000..4c9dbf6133386 --- /dev/null +++ b/errors/no-styled-jsx-in-document.md @@ -0,0 +1,41 @@ +# No styled-jsx in Document + +### Why This Error Occurred + +Custom CSS like `styled-jsx` is not allowed in a [Custom Document](https://nextjs.org/docs/advanced-features/custom-document). + +### Possible Ways to Fix It + +If you need shared CSS for all of your pages, take a look at the [Custom `App`](https://nextjs.org/docs/advanced-features/custom-app) file or define a custom layout. + +For example, consider the following stylesheet named `styles.css`: + +```css +body { + font-family: 'SF Pro Text', 'SF Pro Icons', 'Helvetica Neue', 'Helvetica', + 'Arial', sans-serif; + padding: 20px 20px 60px; + max-width: 680px; + margin: 0 auto; +} +``` + +Create a `pages/_app.{js,tsx}` file if not already present. Then, import the `styles.css` file. + +```jsx +import '../styles.css' + +// This default export is required in a new `pages/_app.js` file. +export default function MyApp({ Component, pageProps }) { + return +} +``` + +These styles (`styles.css`) will apply to all pages and components in your application. + +### Useful links + +- [Custom Document Caveats](https://nextjs.org/docs/advanced-features/custom-document#caveats) +- [Layouts](https://nextjs.org/docs/basic-features/layouts) +- [Built in CSS Support](https://nextjs.org/docs/basic-features/built-in-css-support) +- [Custom `App`](https://nextjs.org/docs/advanced-features/custom-app) diff --git a/packages/eslint-plugin-next/lib/index.js b/packages/eslint-plugin-next/lib/index.js index 84b77fddeb32c..9c1b1559b8fc4 100644 --- a/packages/eslint-plugin-next/lib/index.js +++ b/packages/eslint-plugin-next/lib/index.js @@ -14,6 +14,7 @@ module.exports = { 'no-head-import-in-document': require('./rules/no-head-import-in-document'), 'no-script-component-in-head': require('./rules/no-script-component-in-head'), 'no-server-import-in-page': require('./rules/no-server-import-in-page'), + 'no-styled-jsx-in-document': require('./rules/no-styled-jsx-in-document'), 'no-typos': require('./rules/no-typos'), 'no-duplicate-head': require('./rules/no-duplicate-head'), 'inline-script-id': require('./rules/inline-script-id'), @@ -39,6 +40,7 @@ module.exports = { '@next/next/no-document-import-in-page': 2, '@next/next/no-head-import-in-document': 2, '@next/next/no-script-component-in-head': 2, + '@next/next/no-styled-jsx-in-document': 2, '@next/next/no-server-import-in-page': 2, '@next/next/no-typos': 1, '@next/next/no-duplicate-head': 2, diff --git a/packages/eslint-plugin-next/lib/rules/no-styled-jsx-in-document.js b/packages/eslint-plugin-next/lib/rules/no-styled-jsx-in-document.js new file mode 100644 index 0000000000000..5851c80cb1e83 --- /dev/null +++ b/packages/eslint-plugin-next/lib/rules/no-styled-jsx-in-document.js @@ -0,0 +1,46 @@ +const path = require('path') + +module.exports = { + meta: { + docs: { + description: 'Disallow using custom styled-jsx inside pages/_document.js', + recommended: true, + url: 'https://nextjs.org/docs/messages/no-styled-jsx-in-document', + }, + fixable: 'code', + }, + create: function (context) { + return { + JSXOpeningElement(node) { + const document = context.getFilename().split('pages')[1] + if (!document) { + return + } + const { name, dir } = path.parse(document) + + if ( + !( + name.startsWith('_document') || + (dir === '/_document' && name === 'index') + ) + ) { + return + } + + if ( + node.name.name === 'style' && + node.attributes.find( + (attr) => attr.type === 'JSXAttribute' && attr.name.name === 'jsx' + ) + ) { + context.report({ + node, + message: `styled-jsx can not be used inside pages/_document.js. See https://nextjs.org/docs/messages/no-styled-jsx-in-document.`, + }) + } + }, + } + }, +} + +module.exports.schema = [] diff --git a/test/unit/eslint-plugin-next/no-styled-jsx-in-document.test.ts b/test/unit/eslint-plugin-next/no-styled-jsx-in-document.test.ts new file mode 100644 index 0000000000000..b6cce7043dbe1 --- /dev/null +++ b/test/unit/eslint-plugin-next/no-styled-jsx-in-document.test.ts @@ -0,0 +1,125 @@ +import rule from '@next/eslint-plugin-next/lib/rules/no-styled-jsx-in-document' +import { RuleTester } from 'eslint' +;(RuleTester as any).setDefaultConfig({ + parserOptions: { + ecmaVersion: 2018, + sourceType: 'module', + ecmaFeatures: { + modules: true, + jsx: true, + }, + }, +}) +const ruleTester = new RuleTester() + +ruleTester.run('no-styled-jsx-in-document', rule, { + valid: [ + { + filename: 'pages/_document.js', + code: `import Document, { Html, Head, Main, NextScript } from 'next/document' + + export class MyDocument extends Document { + static async getInitialProps(ctx) { + const initialProps = await Document.getInitialProps(ctx) + return { ...initialProps } + } + + render() { + return ( + + + +
+ + + + ) + } + }`, + }, + { + filename: 'pages/_document.js', + code: `import Document, { Html, Head, Main, NextScript } from 'next/document' + + export class MyDocument extends Document { + static async getInitialProps(ctx) { + const initialProps = await Document.getInitialProps(ctx) + return { ...initialProps } + } + + render() { + return ( + + + + + +
+ + + + ) + } + }`, + }, + { + filename: 'pages/index.js', + code: ` + export default function Page() { + return ( + <> +

Hello world

+ + + ) + } + `, + }, + ], + + invalid: [ + { + filename: 'pages/_document.js', + code: ` + import Document, { Html, Head, Main, NextScript } from 'next/document' + + export class MyDocument extends Document { + static async getInitialProps(ctx) { + const initialProps = await Document.getInitialProps(ctx) + return { ...initialProps } + } + + render() { + return ( + + + + +
+ + + + ) + } + }`, + errors: [ + { + message: + 'styled-jsx can not be used inside pages/_document.js. See https://nextjs.org/docs/messages/no-styled-jsx-in-document.', + }, + ], + }, + ], +})