-
Notifications
You must be signed in to change notification settings - Fork 27k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
ESLint Plugin: Custom Font at page-level rule (#24789)
Adds a lint rule warning to the Next.js ESLint plugin if a custom Google Font is added at page-level instead of with a custom document (`.document.js`) _Note: This will be generalized to include more font providers in the near future._
- Loading branch information
1 parent
569da9d
commit 4407220
Showing
5 changed files
with
201 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
# No Page Custom Font | ||
|
||
### Why This Error Occurred | ||
|
||
A custom font was added to a page and not with a custom `Document`. This only adds the font to the specific page and not to the entire application. | ||
|
||
### Possible Ways to Fix It | ||
|
||
Create the file `./pages/document.js` and add the font to a custom Document: | ||
|
||
```jsx | ||
// pages/_document.js | ||
|
||
import Document, { Html, Head, Main, NextScript } from 'next/document' | ||
|
||
class MyDocument extends Document { | ||
render() { | ||
return ( | ||
<Html> | ||
<Head> | ||
<link | ||
href="https://fonts.googleapis.com/css2?family=Inter&display=optional" | ||
rel="stylesheet" | ||
/> | ||
</Head> | ||
<body> | ||
<Main /> | ||
<NextScript /> | ||
</body> | ||
</Html> | ||
) | ||
} | ||
} | ||
|
||
export default MyDocument | ||
``` | ||
|
||
### When Not To Use It | ||
|
||
If you have a reason to only load a font for a particular page, then you can disable this rule. | ||
|
||
### Useful Links | ||
|
||
- [Custom Document](https://nextjs.org/docs/advanced-features/custom-document) | ||
- [Font Optimization](https://nextjs.org/docs/basic-features/font-optimization) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
55 changes: 55 additions & 0 deletions
55
packages/eslint-plugin-next/lib/rules/no-page-custom-font.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
const NodeAttributes = require('../utils/nodeAttributes.js') | ||
|
||
module.exports = { | ||
meta: { | ||
docs: { | ||
description: | ||
'Recommend adding custom font in a custom document and not in a specific page', | ||
recommended: true, | ||
}, | ||
}, | ||
create: function (context) { | ||
let documentImport = false | ||
return { | ||
ImportDeclaration(node) { | ||
if (node.source.value === 'next/document') { | ||
if (node.specifiers.some(({ local }) => local.name === 'Document')) { | ||
documentImport = true | ||
} | ||
} | ||
}, | ||
JSXOpeningElement(node) { | ||
const documentClass = context | ||
.getAncestors() | ||
.find( | ||
(ancestorNode) => | ||
ancestorNode.type === 'ClassDeclaration' && | ||
ancestorNode.superClass && | ||
ancestorNode.superClass.name === 'Document' | ||
) | ||
|
||
if ((documentImport && documentClass) || node.name.name !== 'link') { | ||
return | ||
} | ||
|
||
const attributes = new NodeAttributes(node) | ||
if (!attributes.has('href') || !attributes.hasValue('href')) { | ||
return | ||
} | ||
|
||
const hrefValue = attributes.value('href') | ||
const isGoogleFont = hrefValue.includes( | ||
'https://fonts.googleapis.com/css' | ||
) | ||
|
||
if (isGoogleFont) { | ||
context.report({ | ||
node, | ||
message: | ||
'Custom fonts should be added at the document level. See https://nextjs.org/docs/messages/no-page-custom-font.', | ||
}) | ||
} | ||
}, | ||
} | ||
}, | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,95 @@ | ||
const rule = require('@next/eslint-plugin-next/lib/rules/no-page-custom-font') | ||
const RuleTester = require('eslint').RuleTester | ||
|
||
RuleTester.setDefaultConfig({ | ||
parserOptions: { | ||
ecmaVersion: 2018, | ||
sourceType: 'module', | ||
ecmaFeatures: { | ||
modules: true, | ||
jsx: true, | ||
}, | ||
}, | ||
}) | ||
|
||
var ruleTester = new RuleTester() | ||
ruleTester.run('no-page-custom-font', rule, { | ||
valid: [ | ||
`import Document, { Html, Head } from "next/document"; | ||
class MyDocument extends Document { | ||
render() { | ||
return ( | ||
<Html> | ||
<Head> | ||
<link | ||
href="https://fonts.googleapis.com/css2?family=Krona+One&display=swap" | ||
rel="stylesheet" | ||
/> | ||
</Head> | ||
</Html> | ||
); | ||
} | ||
} | ||
export default MyDocument; | ||
`, | ||
], | ||
|
||
invalid: [ | ||
{ | ||
code: ` | ||
import Head from 'next/head' | ||
export default function IndexPage() { | ||
return ( | ||
<div> | ||
<Head> | ||
<link | ||
href="https://fonts.googleapis.com/css2?family=Inter" | ||
rel="stylesheet" | ||
/> | ||
</Head> | ||
<p>Hello world!</p> | ||
</div> | ||
) | ||
} | ||
`, | ||
errors: [ | ||
{ | ||
message: | ||
'Custom fonts should be added at the document level. See https://nextjs.org/docs/messages/no-page-custom-font.', | ||
type: 'JSXOpeningElement', | ||
}, | ||
], | ||
}, | ||
{ | ||
code: ` | ||
import Document, { Html, Head } from "next/document"; | ||
class MyDocument { | ||
render() { | ||
return ( | ||
<Html> | ||
<Head> | ||
<link | ||
href="https://fonts.googleapis.com/css2?family=Krona+One&display=swap" | ||
rel="stylesheet" | ||
/> | ||
</Head> | ||
</Html> | ||
); | ||
} | ||
} | ||
export default MyDocument;`, | ||
errors: [ | ||
{ | ||
message: | ||
'Custom fonts should be added at the document level. See https://nextjs.org/docs/messages/no-page-custom-font.', | ||
type: 'JSXOpeningElement', | ||
}, | ||
], | ||
}, | ||
], | ||
}) |