Skip to content

Commit

Permalink
[ESLint] Adds lint rule to flag usage of <head> (vercel#32897)
Browse files Browse the repository at this point in the history
## Bug

- [X] Related issues linked using `fixes #number`
- [X] Tests added
- [X] Errors have helpful link attached, see `contributing.md`

Fixes vercel#30142
  • Loading branch information
housseindjirdeh authored Dec 31, 2021
1 parent 636d3ae commit d72f5bd
Show file tree
Hide file tree
Showing 6 changed files with 162 additions and 0 deletions.
1 change: 1 addition & 0 deletions docs/basic-features/eslint.md
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ Next.js provides an ESLint plugin, [`eslint-plugin-next`](https://www.npmjs.com/
| ✔️ | [next/no-head-import-in-document](https://nextjs.org/docs/messages/no-head-import-in-document) | Disallow importing next/head in pages/document.js |
| ✔️ | [next/no-html-link-for-pages](https://nextjs.org/docs/messages/no-html-link-for-pages) | Prohibit HTML anchor links to pages without a Link component |
| ✔️ | [next/no-img-element](https://nextjs.org/docs/messages/no-img-element) | Prohibit usage of HTML &lt;img&gt; element |
| ✔️ | [next/no-head-element](https://nextjs.org/docs/messages/no-head-element) | Prohibit usage of HTML &lt;head&gt; element |
| ✔️ | [next/no-page-custom-font](https://nextjs.org/docs/messages/no-page-custom-font) | Prevent page-only custom fonts |
| ✔️ | [next/no-sync-scripts](https://nextjs.org/docs/messages/no-sync-scripts) | Forbid synchronous scripts |
| ✔️ | [next/no-title-in-document-head](https://nextjs.org/docs/messages/no-title-in-document-head) | Disallow using &lt;title&gt; with Head from next/document |
Expand Down
1 change: 1 addition & 0 deletions errors/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -447,6 +447,7 @@
"path": "/errors/link-multiple-children.md"
},
{ "title": "no-img-element", "path": "/errors/no-img-element.md" },
{ "title": "no-head-element", "path": "/errors/no-head-element.md" },
{
"title": "non-dynamic-getstaticpaths-usage",
"path": "/errors/non-dynamic-getstaticpaths-usage.md"
Expand Down
30 changes: 30 additions & 0 deletions errors/no-head-element.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# No Head Element

### Why This Error Occurred

An HTML `<head>` element was used to include page-level metadata, but this can cause unexpected behavior in a Next.js application. Use Next.js' built-in `<Head />` component instead.

### Possible Ways to Fix It

Import and use the `<Head />` component:

```jsx
import Head from 'next/head'

function Index() {
return (
<>
<Head>
<title>My page title</title>
<meta name="viewport" content="initial-scale=1.0, width=device-width" />
</Head>
</>
)
}

export default Index
```

### Useful Links

- [next/head](https://nextjs.org/docs/api-reference/next/head)
2 changes: 2 additions & 0 deletions packages/eslint-plugin-next/lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ module.exports = {
'no-sync-scripts': require('./rules/no-sync-scripts'),
'no-html-link-for-pages': require('./rules/no-html-link-for-pages'),
'no-img-element': require('./rules/no-img-element'),
'no-head-element': require('./rules/no-head-element'),
'no-unwanted-polyfillio': require('./rules/no-unwanted-polyfillio'),
'no-page-custom-font': require('./rules/no-page-custom-font'),
'no-title-in-document-head': require('./rules/no-title-in-document-head'),
Expand All @@ -28,6 +29,7 @@ module.exports = {
'@next/next/no-sync-scripts': 1,
'@next/next/no-html-link-for-pages': 1,
'@next/next/no-img-element': 1,
'@next/next/no-head-element': 1,
'@next/next/no-unwanted-polyfillio': 1,
'@next/next/no-page-custom-font': 1,
'@next/next/no-title-in-document-head': 1,
Expand Down
26 changes: 26 additions & 0 deletions packages/eslint-plugin-next/lib/rules/no-head-element.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
module.exports = {
meta: {
docs: {
description: 'Prohibit usage of HTML <head> element',
category: 'HTML',
recommended: true,
url: 'https://nextjs.org/docs/messages/no-head-element',
},
fixable: 'code',
},

create: function (context) {
return {
JSXOpeningElement(node) {
if (node.name.name !== 'head') {
return
}

context.report({
node,
message: `Do not use <head>. Use Head from 'next/head' instead. See: https://nextjs.org/docs/messages/no-head-element`,
})
},
}
},
}
102 changes: 102 additions & 0 deletions test/unit/eslint-plugin-next/no-head-element.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
import rule from '@next/eslint-plugin-next/lib/rules/no-head-element'
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-head-element', rule, {
valid: [
{
code: `import Head from 'next/head';
export class MyComponent {
render() {
return (
<div>
<Head>
<title>My page title</title>
</Head>
</div>
);
}
}
`,
filename: 'pages/index.js',
},
{
code: `import Head from 'next/head';
export class MyComponent {
render() {
return (
<div>
<Head>
<title>My page title</title>
</Head>
</div>
);
}
}
`,
filename: 'pages/index.tsx',
},
],
invalid: [
{
code: `
export class MyComponent {
render() {
return (
<div>
<head>
<title>My page title</title>
</head>
</div>
);
}
}`,
filename: 'pages/index.js',
errors: [
{
message:
"Do not use <head>. Use Head from 'next/head' instead. See: https://nextjs.org/docs/messages/no-head-element",
type: 'JSXOpeningElement',
},
],
},
{
code: `import Head from 'next/head';
export class MyComponent {
render() {
return (
<div>
<head>
<title>My page title</title>
</head>
<Head>
<title>My page title</title>
</Head>
</div>
);
}
}`,
filename: 'pages/index.ts',
errors: [
{
message:
"Do not use <head>. Use Head from 'next/head' instead. See: https://nextjs.org/docs/messages/no-head-element",
type: 'JSXOpeningElement',
},
],
},
],
})

0 comments on commit d72f5bd

Please sign in to comment.