Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ESLint Plugin: Prevent bad imports of next/document and next/head #24832

Merged
merged 6 commits into from
May 10, 2021
Merged
Show file tree
Hide file tree
Changes from 5 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
8 changes: 8 additions & 0 deletions errors/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,10 @@
},
{ "title": "no-cache", "path": "/errors/no-cache.md" },
{ "title": "no-css-tags", "path": "/errors/no-css-tags.md" },
{
"title": "no-document-import-in-page",
"path": "/errors/no-document-import-in-page.md"
},
{
"title": "no-document-title",
"path": "/errors/no-document-title.md"
Expand All @@ -263,6 +267,10 @@
"title": "no-document-viewport-meta",
"path": "/errors/no-document-viewport-meta.md"
},
{
"title": "no-head-import-in-document",
"path": "/errors/no-head-import-in-document.md"
},
{
"title": "no-html-link-for-pages",
"path": "/errors/no-html-link-for-pages.md"
Expand Down
24 changes: 24 additions & 0 deletions errors/no-document-import-in-page.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# No Document Import in Page

### Why This Error Occurred

`next/document` was imported in a page outside of `pages/_document.js`. This can cause unexpected issues in your application.

### Possible Ways to Fix It

Only import and use `next/document` within `pages/_document.js` to override the default `Document` component:

```jsx
// pages/_document.js
import Document, { Html, Head, Main, NextScript } from 'next/document'

class MyDocument extends Document {
//...
}

export default MyDocument
```

### Useful Links

- [Custom Document](https://nextjs.org/docs/advanced-features/custom-document)
34 changes: 34 additions & 0 deletions errors/no-head-import-in-document.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# No Head Import in Document

### Why This Error Occurred

`next/head` was imported in `pages/_document.js`. This can cause unexpected issues in your application.

### Possible Ways to Fix It

Only import and use `next/document` within `pages/_document.js` to override the default `Document` component. If you are importing `next/head` to use the `Head` component, import it from `next/document` instead in order to modify `<head>` code across all pages:

```jsx
// pages/_document.js
import Document, { Html, Head, Main, NextScript } from 'next/document'

class MyDocument extends Document {
static async getInitialProps(ctx) {
//...
}

render() {
return (
<Html>
<Head></Head>
</Html>
)
}
}

export default MyDocument
```

### Useful Links

- [Custom Document](https://nextjs.org/docs/advanced-features/custom-document)
4 changes: 4 additions & 0 deletions packages/eslint-plugin-next/lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ module.exports = {
'google-font-display': require('./rules/google-font-display'),
'google-font-preconnect': require('./rules/google-font-preconnect'),
'link-passhref': require('./rules/link-passhref'),
'no-document-import-in-page': require('./rules/no-document-import-in-page'),
'no-head-import-in-document': require('./rules/no-head-import-in-document'),
},
configs: {
recommended: {
Expand All @@ -23,6 +25,8 @@ module.exports = {
'@next/next/google-font-display': 1,
'@next/next/google-font-preconnect': 1,
'@next/next/link-passhref': 1,
'@next/next/no-document-import-in-page': 2,
'@next/next/no-head-import-in-document': 2,
},
},
},
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
const path = require('path')

module.exports = {
meta: {
docs: {
description:
'Disallow importing next/document outside of pages/document.js',
recommended: true,
},
},
create: function (context) {
return {
ImportDeclaration(node) {
if (node.source.value !== 'next/document') {
return
}

const page = context.getFilename().split('pages')[1]
if (!page || path.parse(page).name === '_document') {
return
}

context.report({
node,
message: `next/document should not be imported outside of pages/_document.js. See https://nextjs.org/docs/messages/no-document-import-in-page.`,
})
},
}
},
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
const path = require('path')

module.exports = {
meta: {
docs: {
description: 'Disallow importing next/head in pages/document.js',
recommended: true,
},
},
create: function (context) {
return {
ImportDeclaration(node) {
if (node.source.value !== 'next/head') {
return
}

const document = context.getFilename().split('pages')[1]
if (!document || path.parse(document).name !== '_document') {
return
}

context.report({
node,
message: `next/head should not be imported in pages${document}. Import Head from next/document instead. See https://nextjs.org/docs/messages/no-head-import-in-document.`,
})
},
}
},
}
66 changes: 66 additions & 0 deletions test/eslint-plugin-next/no-document-import-in-page.unit.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
const rule = require('@next/eslint-plugin-next/lib/rules/no-document-import-in-page')

const RuleTester = require('eslint').RuleTester

RuleTester.setDefaultConfig({
parserOptions: {
ecmaVersion: 2018,
sourceType: 'module',
ecmaFeatures: {
modules: true,
jsx: true,
},
},
})

var ruleTester = new RuleTester()
ruleTester.run('no-document-import-in-page', rule, {
valid: [
{
code: `import Document from "next/document"
export default class MyDocument extends Document {
render() {
return (
<Html>
</Html>
);
}
}
`,
filename: 'pages/_document.js',
},
{
code: `import Document from "next/document"
export default class MyDocument extends Document {
render() {
return (
<Html>
</Html>
);
}
}
`,
filename: 'pages/_document.tsx',
},
],
invalid: [
{
code: `import Document from "next/document"
export const Test = () => (
<p>Test</p>
)
`,
filename: 'pages/test.js',
errors: [
{
message:
'next/document should not be imported outside of pages/_document.js. See https://nextjs.org/docs/messages/no-document-import-in-page.',
type: 'ImportDeclaration',
},
],
},
],
})
88 changes: 88 additions & 0 deletions test/eslint-plugin-next/no-head-import-in-document.unit.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
const rule = require('@next/eslint-plugin-next/lib/rules/no-head-import-in-document')

const RuleTester = require('eslint').RuleTester

RuleTester.setDefaultConfig({
parserOptions: {
ecmaVersion: 2018,
sourceType: 'module',
ecmaFeatures: {
modules: true,
jsx: true,
},
},
})

var ruleTester = new RuleTester()
ruleTester.run('no-head-import-in-document', rule, {
valid: [
{
code: `import Document, { Html, Head, Main, NextScript } from 'next/document'
class MyDocument extends Document {
static async getInitialProps(ctx) {
//...
}
render() {
return (
<Html>
<Head>
</Head>
</Html>
)
}
}
export default MyDocument
`,
filename: 'pages/_document.tsx',
},
{
code: `import Head from "next/head";
export default function IndexPage() {
return (
<Head>
<title>My page title</title>
<meta name="viewport" content="initial-scale=1.0, width=device-width" />
</Head>
);
}
`,
filename: 'pages/index.tsx',
},
],
invalid: [
{
code: `
import Document, { Html, Main, NextScript } from 'next/document'
import Head from 'next/head'
class MyDocument extends Document {
render() {
return (
<Html>
<Head />
<body>
<Main />
<NextScript />
</body>
</Html>
)
}
}
export default MyDocument
`,
filename: 'pages/_document.js',
errors: [
{
message:
'next/head should not be imported in pages/_document.js. Import Head from next/document instead. See https://nextjs.org/docs/messages/no-head-import-in-document.',
type: 'ImportDeclaration',
},
],
},
],
})