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 `` 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 (
+
+
+
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 (
+
+ 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',
+ },
+ ],
+ },
+ ],
+})