Official in-tree examples
As mentioned by GorvGoyl, some better integration was added around Next.js 11.
Although there is documentation at: https://nextjs.org/docs/basic-features/eslint being a eslint newbie I just couldn't understand what I was supposed to do, so I just looked under examples/ and found some actual working code:
examples/with-eslint
https://github.com/vercel/next.js/tree/v12.0.7/examples/with-eslint
Minimal eslint example.
The setup contains:
package.json
{
"name": "with-eslint",
"version": "1.0.0",
"scripts": {
"dev": "next",
"build": "next build",
"start": "next start",
"lint": "next lint"
},
"license": "MIT",
"dependencies": {
"next": "12.0.7",
"react": "^17.0.2",
"react-dom": "^17.0.2"
},
"devDependencies": {
"eslint": "^7.24.0",
"eslint-config-next": "12.0.7"
}
}
.eslintrc
{
"extends": "next",
"root": true
}
pages/index.js
const Home = () => (
<div>
<script src="https://fake-script.com" />
<p>Home</p>
</div>
)
export default Home
For example doing:
cd examples/with-eslint
npm install
npm run lint
gives the desired error:
3:5 Warning: External synchronous scripts are forbidden. See: https://nextjs.org/docs/messages/no-sync-scripts. @next/next/no-sync-scripts
We can modify pages/index.js a bit to add some more cases that we might want to fail:
import React from 'react'
const Home = () => {
let s = 'abc'
s = "abc"
let unused
if (false) {
React.useEffect(() => 1)
}
return (
<div>
<script src="https://fake-script.com" />
<p>Home</p>
</div>
)
}
export default Home
and the results are:
examples/with-typescript-eslint-jest
https://github.com/vercel/next.js/tree/v12.0.7/examples/with-typescript-eslint-jest
Exmple also with typescript.
Note that this example cannot be run in-tree otherwise it fails with:
ESLint couldn't find the plugin "eslint-plugin-jest".
which is must be picking up from repo toplevel:
The plugin "eslint-plugin-jest" was referenced from the config file in "../../.eslintrc.json#overrides[0]".
you have to first copy it outside somewhere like:
cp -rv examples/with-typescript-eslint-jest /tmp
cd /tmp/with-typescript-eslint-jest
But I think this examples is a bit outdated as it is not using the"
"extends": "next",
preset.
My recommended Next 12 typescript + prettier setup
Since the in-tree "examples/with-typescript-eslint-jest" doesn't look very up-to-date, here's a version of it that should be (just without jest):
package.json
{
"name": "with-eslint",
"version": "1.0.0",
"scripts": {
"dev": "next",
"build": "next build",
"start": "next start",
"lint": "next lint",
"format": "prettier --ignore-path .gitignore --write .",
"type-check": "tsc"
},
"license": "MIT",
"dependencies": {
"install": "0.13.0",
"next": "12.0.7",
"react": "17.0.2",
"react-dom": "17.0.2"
},
"devDependencies": {
"@types/node": "12.12.21",
"@types/react": "17.0.2",
"@types/react-dom": "17.0.1",
"eslint": "8.5.0",
"eslint-config-next": "12.0.7",
"eslint-config-prettier": "7.2.0",
"eslint-plugin-prettier": "4.0.0",
"prettier": "2.5.1",
"typescript": "4.5.4"
},
"prettier": {
"printWidth": 80,
"semi": false,
"singleQuote": true
}
}
.eslintrc.json
{
"extends": ["eslint:recommended", "next", "prettier"],
"plugins": ["prettier"],
"rules": {
"prettier/prettier": "error"
}
}
tsconfig.json (autogenerated by Next when you run npm run dev)
{
"compilerOptions": {
"target": "es5",
"lib": [
"dom",
"dom.iterable",
"esnext"
],
"allowJs": true,
"skipLibCheck": true,
"strict": false,
"forceConsistentCasingInFileNames": true,
"noEmit": true,
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve",
"incremental": true
},
"include": [
"next-env.d.ts",
"**/*.ts",
"**/*.tsx"
],
"exclude": [
"node_modules"
]
}
.gitignore
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.js
# testing
/coverage
# next.js
/.next/
/out/
# production
/build
# misc
.DS_Store
*.pem
# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# local env files
.env.local
.env.development.local
.env.test.local
.env.production.local
# vercel
.vercel
# typescript
*.tsbuildinfo
pages/index.tsx
import React from 'react'
const Home = () => {
let n: number
let s = 'abc'
s = "abc"
let unused
if (false) {
React.useEffect(() => 1)
}
return (
<div>
<script src="https://fake-script.com" />
<p>Home</p>
</div>
)
}
export default Home
With this setup doing:
npm run lint
catches all issues as we would like:
4:7 Error: 'n' is defined but never used. no-unused-vars
6:3 Error: 's' is assigned a value but never used. no-unused-vars
6:7 Error: Replace `"abc"` with `'abc'` prettier/prettier
7:7 Error: 'unused' is defined but never used. no-unused-vars
8:7 Error: Unexpected constant condition. no-constant-condition
9:5 Error: React Hook "React.useEffect" is called conditionally. React Hooks must be called in the exact same order in every component render. react-hooks/rules-of-hooks
13:7 Warning: External synchronous scripts are forbidden. See: https://nextjs.org/docs/messages/no-sync-scripts. @next/next/no-sync-scripts
The prettier errors can be fixed automatically with autoformatting:
npm run format
which automatically patches for us:
--- a/pages/index.tsx
+++ b/pages/index.tsx
@@ -3,7 +3,7 @@ import React from 'react'
const Home = () => {
let n: number
let s = 'abc'
- s = "abc"
+ s = 'abc'
let unused
if (false) {
Running:
npm run type-check
catches the type error:
pages/index.tsx:9:27 - error TS2322: Type 'number' is not assignable to type 'void | Destructor'.
9 React.useEffect(() => 1)
Both npm run lint and npm run type-check are automatically run by npm run build.
Lint is enabled only on pages/, components/ and lib/ by default
As mentioned at: https://nextjs.org/docs/basic-features/eslint#linting-custom-directories-and-files
To enable it everywhere in the projects:
module.exports = {
eslint: {
dirs: ['.'],
},
}