diff --git a/errors/manifest.json b/errors/manifest.json index 431783539f372..70862c14293a0 100644 --- a/errors/manifest.json +++ b/errors/manifest.json @@ -267,6 +267,10 @@ "title": "no-sync-scripts", "path": "/errors/no-sync-scripts.md" }, + { + "title": "no-title-in-document-head", + "path": "/errors/no-title-in-document-head.md" + }, { "title": "no-unwanted-polyfillio", "path": "/errors/no-unwanted-polyfillio.md" diff --git a/errors/no-title-in-document-head.md b/errors/no-title-in-document-head.md new file mode 100644 index 0000000000000..771e99674d5c4 --- /dev/null +++ b/errors/no-title-in-document-head.md @@ -0,0 +1,30 @@ +# No Title in Document Head + +### Why This Error Occurred + +A `` element was defined within the `Head` component imported from `next/document`, which should only be used for any `<head>` code that is common for all pages. Title tags should be defined at the page-level using `next/head`. + +### Possible Ways to Fix It + +Within a page or component, import and use `next/head` to define a page title: + +```jsx +import Head from 'next/head' + +export class Home { + render() { + return ( + <div> + <Head> + <title>My page title + + + ) + } +} +``` + +### Useful links + +- [next/head](https://nextjs.org/docs/api-reference/next/head) +- [Custom Document](https://nextjs.org/docs/advanced-features/custom-document) diff --git a/packages/eslint-plugin-next/lib/index.js b/packages/eslint-plugin-next/lib/index.js index 876aa2e91630e..4565a833fe11d 100644 --- a/packages/eslint-plugin-next/lib/index.js +++ b/packages/eslint-plugin-next/lib/index.js @@ -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-unwanted-polyfillio': require('./rules/no-unwanted-polyfillio'), + 'no-title-in-document-head': require('./rules/no-title-in-document-head'), }, configs: { recommended: { @@ -13,6 +14,7 @@ module.exports = { '@next/next/no-sync-scripts': 1, '@next/next/no-html-link-for-pages': 1, '@next/next/no-unwanted-polyfillio': 1, + '@next/next/no-title-in-document-head': 1, }, }, }, diff --git a/packages/eslint-plugin-next/lib/rules/no-title-in-document-head.js b/packages/eslint-plugin-next/lib/rules/no-title-in-document-head.js new file mode 100644 index 0000000000000..a945566d7c8ee --- /dev/null +++ b/packages/eslint-plugin-next/lib/rules/no-title-in-document-head.js @@ -0,0 +1,48 @@ +module.exports = { + meta: { + docs: { + description: 'Disallow using with Head from next/document', + }, + }, + create: function (context) { + let headFromNextDocument = false + return { + ImportDeclaration(node) { + if (node.source.value === 'next/document') { + if (node.specifiers.some(({ local }) => local.name === 'Head')) { + headFromNextDocument = true + } + } + }, + JSXElement(node) { + if (!headFromNextDocument) { + return + } + + if ( + node.openingElement && + node.openingElement.name && + node.openingElement.name.name !== 'Head' + ) { + return + } + + const titleTag = node.children.find( + (child) => + child.openingElement && + child.openingElement.name && + child.openingElement.name.type === 'JSXIdentifier' && + child.openingElement.name.name === 'title' + ) + + if (titleTag) { + context.report({ + node: titleTag, + message: + 'Titles should be defined at the page-level using next/head. See https://nextjs.org/docs/messages/no-title-in-document-head.', + }) + } + }, + } + }, +} diff --git a/test/eslint-plugin-next/no-title-in-document-head.unit.test.js b/test/eslint-plugin-next/no-title-in-document-head.unit.test.js new file mode 100644 index 0000000000000..b20f5fa358ab6 --- /dev/null +++ b/test/eslint-plugin-next/no-title-in-document-head.unit.test.js @@ -0,0 +1,70 @@ +const rule = require('@next/eslint-plugin-next/lib/rules/no-title-in-document-head') +const RuleTester = require('eslint').RuleTester + +RuleTester.setDefaultConfig({ + parserOptions: { + ecmaVersion: 2018, + sourceType: 'module', + ecmaFeatures: { + modules: true, + jsx: true, + }, + }, +}) + +var ruleTester = new RuleTester() +ruleTester.run('no-title-in-document-head', rule, { + valid: [ + `import Head from "next/head"; + + class Test { + render() { + return ( + <Head> + <title>My page title + + ); + } + }`, + + `import Document, { Html, Head } from "next/document"; + + class MyDocument extends Document { + render() { + return ( + + + + + ); + } + } + + export default MyDocument; + `, + ], + + invalid: [ + { + code: ` + import { Head } from "next/document"; + + class Test { + render() { + return ( + + My page title + + ); + } + }`, + errors: [ + { + message: + 'Titles should be defined at the page-level using next/head. See https://nextjs.org/docs/messages/no-title-in-document-head.', + type: 'JSXElement', + }, + ], + }, + ], +})