[2023年]Next.js + eslint周りの設定

2023/04/23に公開

更新履歴

  • 2024年3月31日 → npm scriptsについて追記しました。
  • 2024年6月16日 → eslintの破壊的変更について追記しました。
  • 2024年10月28日 → 「[2024年]Next.js(v15) + ESLint(v9 FlatConfig )周りの設定」の記事へのリンクを追加しました。

追記その2(2024年10月28日)

2024年版のESLint(v9 FlatConfig)に対応したバージョンの記事を書きました。

https://zenn.dev/resistance_gowy/articles/16828fa82a49ac

追記(2024年6月16日)

eslintのバージョンを上げたら、めちゃくちゃ設定ファイルに変更が入っています。
詳細は以下の記事が詳しいです。
これにそって、設定ファイルを修正する必要があります。

https://zenn.dev/ikemo/scraps/ad6a3dcdf90055
https://zenn.dev/babel/articles/eslint-flat-config-for-babel
https://typescript-eslint.io/troubleshooting/formatting/
https://eslint.org/docs/latest/use/configure/configuration-files
https://eslint.org/docs/latest/use/configure/migration-guide#ignoring-files

Flat Config Files という設定方法に変わっていて、
書き方と読み込むライブラリが大幅に変わっています。
下は雰囲気をつかんでもらうためのサンプルです。(Next.jsのではないので注意!)

  • 設定ファイル名は、eslint.config.{js,cjs,mjs} しかダメになった。
  • Flat Config Files という書き方になった
  • ignoreの書き方が変わった(.eslintignoreは廃止)。
eslint.config.js
// Next.jsのではないので注意!

import js from '@eslint/js';
import tseslint from 'typescript-eslint';
import prettierConfig from 'eslint-config-prettier';

export default [
  js.configs.recommended,
  ...tseslint.configs.recommended,
  prettierConfig,
  {
    ignores: ['**/.*', 'wp-content/*', 'esbuild.config.js'],
  },
  {
    rules: {
      'no-const-assign': 'error',
      '@typescript-eslint/no-unused-vars': 'warn',
    },
  },
];

あとは、.vscode/settings.json でflatConfigの設定が必要です。

https://qiita.com/Shilaca/items/c494e4dc6b536a5231de#vscode-の-eslint-extension-で-flat-config-を有効化する

.vscode/settings.json
{
  "eslint.experimental.useFlatConfig": true
}

動機

リンター周りの設定で理解が曖昧な箇所があったので、
クリアにして諸々の設定を見直したい

と考えたからです。

リンターや、フォーマッターで他人の設定をコピペして設定している人も多いかと思います。
それだと応用が効かなかったり、古い設定をそのまま使用することになってしまいます。

方針

  • 最小限の設定から足りないものを足していくスタイル
  • 魔改造しない
  • これはやっといた方がいいものだけに絞る
  • 一個一個噛み砕いて、設定していく

成果物

こちらに成果物のリポジトリを用意しました。
記事を読むのは面倒な人はこちらからどうぞ。
commitは記事の順番にそっているので、記事の流れに沿って設定できます。

https://github.com/underground0930/nextjs-eslint-2023

(1)Next.jsをインストール

npx create-next-app@latest --ts

上記コマンドを打てば、対話形式で設定できます。
appDirだけNoで後は全部Yesにしました。

自分で1から設定するのは大変なので、新規プロジェクトの場合はこちらのコマンドを使いましょう。不要なものは除いてあるので安心です。

package.json
  "dependencies": {
    "@types/node": "18.15.11",
    "@types/react": "18.0.37",
    "@types/react-dom": "18.0.11",
    "autoprefixer": "10.4.14",
    "eslint": "8.38.0",
    "eslint-config-next": "13.3.0",
    "next": "13.3.0",
    "postcss": "8.4.22",
    "react": "18.2.0",
    "react-dom": "18.2.0",
    "tailwindcss": "3.3.1",
    "typescript": "5.0.4"
  }

eslintでは初期からCore Web Vitalsに関する設定が入っています。設定されている
eslint-config-nexteslint-plugin-next の設定を読み込んで拡張しています。

.eslintrc.js
// .eslintrcファイルは扱いやすくするために拡張子を.jsにして以下のように変更します。
/** @type {import('eslint').ESLint.ConfigData} */
module.exports = { extends: "next/core-web-vitals" }

これは、{"extends": "next"} にCore Web Vitalsについてのルールを追加した拡張版になります

"next/core-web-vitals"は以下を読み込んでいます。

https://github.com/vercel/next.js/blob/canary/packages/eslint-config-next/core-web-vitals.js

これを紐解いていくと、

extends[0]

eslint-config-nextの設定を読み込んでいます

# extends[0]
require.resolve('.')
↓
/eslint-config-next/index.js

該当ファイル
https://github.com/vercel/next.js/blob/canary/packages/eslint-config-next/index.js

next/recommendedに加えて、
react/recommended
react-hooks/recommended
も読み込んでいるので、これらのインストールは不要です。

https://github.com/vercel/next.js/blob/canary/packages/eslint-config-next/index.js#L55-L59

extends[1]

eslint-plugin-nextのcore-web-vitalsの設定を読み込んでいます

# extends[1]
'plugin:@next/next/core-web-vitals'
↓
/eslint-plugin-next/src/index.ts

該当ファイルと読み込んでいる設定の箇所
https://github.com/vercel/next.js/blob/canary/packages/eslint-plugin-next/src/index.ts#L52-L59

補足

.eslintignoreファイルも作成しましょう

touch .eslintignore

https://eslint.org/docs/latest/use/configure/ignore#the-eslintignore-file

node_modules はデフォルトでオフになっているようです。
lintから外したいファイルは設定しましょう。

.eslintignore

# config
.eslintrc.js
prettier.config.js
next.config.js
tailwind.config.js
tsconfig.json
postcss.config.js

# build dir
build/
bin/
obj/
out/
.next/

参考

https://nextjs.org/docs/basic-features/eslint#core-web-vitals
https://the2g.com/post/nextjs-11#:~:text=%2C } }-,Core Web Vitals,-Core Web Vitals

追記

ツイートしている方もおられますが、
fetchやlint結果のキャッシュが.next/に残っていて開発の妨げになる時があるので、
以下を、npm scriptsに追記した方が良さそうです。

  "scripts": {
    "predev": "rm -fr .next",
    "dev": "next dev",

https://twitter.com/jherr/status/1758571101964382487
https://twitter.com/RhysSullivan/status/1758588562163794002
https://twitter.com/stin_factory/status/1664117576258174977

windows環境だと動かないので、以下をインストールして
https://www.npmjs.com/package/rimraf

こちらの方が親切ですね。

  "scripts": {
    "predev": "rimraf .next",
    "dev": "next dev",

(2)prettierをインストール

フォーマッターのprettierを入れましょう。最近はdemoのコードを書く時も入れてます。
自分でインデントや改行を編集するのは辛すぎるのと、他人のコードと差分が出てしまいやすいからです。

npm install -D prettier

eslintと設定が衝突する可能性があるので、以下もインストール

npm install -D eslint-config-prettier

.eslintrc.jsonを編集。
prettierは最後に設定しましょう。

.eslintrc.js
/** @type {import('eslint').ESLint.ConfigData} */
module.exports = {
  "extends": ["next/core-web-vitals" , "prettier"]
}

コマンドでフォーマットしてくれるように追加

package.json
  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start",
     "lint": "next lint",
     "format": "prettier --write ."
  },

以下は推奨されていないので、入れません。

eslint-plugin-prettier

https://prettier.io/docs/en/integrating-with-linters.html

設定ファイルを作成します。

touch prettier.config.js

この設定は度々宗教論争になりやすいです。(大体CTOとか偉い人が決めるのでしょうか?)
メンバーで合わせる事が重要なので、何が正解とかは無いですが、ここで強制することによって
(メンバーそれぞれの思惑はともかく笑)コードの一貫性を担保出来るのはいいですね。
文頭にあるコメントを入れると、プロパティの型の補完をエディタがしてくれて便利です。

prettier.config.js
/** @type {import('prettier').Config} */
module.exports = {
  "semi": false, // セミコロン無し
  "singleQuote": true, // シングルクォート使う
  "printWidth": 90, // 折り返し
  "tabWidth": 2, // スペースの単位
  "trailingComma": "all", // ケツカンマつける
  "jsxSingleQuote": true // JSXでシングルクォートを使用
}

.prettierignoreも作成します。

touch .prettierignore

node_modules/ はデフォルトで無視されています。

.prettierignore
# Ignore artifacts:
build
coverage
yarn.lock

# dotfile
.env*

# markdown
*.md

# next.js
/.next/
/out/

# production
/build

# yarn
/.yarn/

参考

https://prettier.io/docs/en/install.html
https://prettier.io/docs/en/options.html
https://jp.quora.com/プログラミングに関する宗教論争にはどのようなも

(3)IDEのeslintとprettierの拡張機能を入れる

私はVScodeを使用していますが、専用の拡張機能を入れています。

eslint

常時errorやwarningを出してくれます。
https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint

prettier

保存時に自動フォーマットが出来ます。
https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode

上記2つの拡張機能の設定をsetting.jsonに設定を加えます

editor.formatOnSave が保存時にフォーマット
editor.codeActionsOnSave.source.fixAll.eslint が保存時にlint --fix
editor.defaultFormatter でデフォルトのフォーマッターを決めています。

その下に言語ごとに設定を上書きできます。

.vscode/settings.json
{
  "editor.formatOnSave": true,
  "editor.codeActionsOnSave": {
    "source.fixAll.eslint": true
  },
  "editor.defaultFormatter": "esbenp.prettier-vscode",
  "[typescriptreact]": {},
  "[typescript]": {},
  "[jsonc]": {},
  "[javascript]": {},
  "[html]": {
    "editor.defaultFormatter": "vscode.html-language-features",
    "editor.formatOnSave": false
  },
  "[ejs]": {
    "editor.formatOnSave": false
  }
}

参考

https://maku.blog/p/qcoz9ju/
https://zenn.dev/k_kazukiiiiii/articles/670ebae0005872
https://zenn.dev/ryusou/articles/nodejs-prettier-eslint2021

プラグインのインストールの強要はあまりしたくないですが、上記2つは必須で入れといてほしい拡張機能です。なので、vscodeのrecommendationsに追加しましょう。
これを入れることで、メンバーに同じ拡張機能のインストールをしてもらうことが簡単に出来るようになります。

.vscode/extensions.json
{
  "recommendations": [
    "dbaeumer.vscode-eslint",
    "esbenp.prettier-vscode"
  ]
}

参考

https://future-architect.github.io/articles/20200828/

(4)eslintで追加で必要なプラグインを設定する

eslint:recommended

現状だと、基本的なjavascriptのlintの設定が入っていません。
たとえば、constの再代入も通ってしまいます。

const x = 'abc'
x = 123  // pass !?

https://eslint.org/docs/latest/rules/no-const-assign

なので、eslintの推奨ルールを追加しましょう(eslintはインストール済みなので)
(eslint:allだと全部盛りなので画面が真っ赤になります)

.eslintrc.js
/** @type {import('eslint').ESLint.ConfigData} */
module.exports = {
  "extends": [
    "eslint:recommended",
   "next/core-web-vitals",
   "prettier"
  ]
}

これで、以下の基本的なエラーも出るようになりました。

13:1  Error: 'x' is constant.  no-const-assign
13:1  Error: 'x' is assigned a value but never used.  no-unused-vars
19:3  Error: 'yy' is not defined.  no-undef

typescript-eslint

現状、Javascriptのlintは入りましたが、Typescriptには対応していません。
eslintに対応させるにはこちらが必要になります。

npm i -D @typescript-eslint/parser @typescript-eslint/eslint-plugin
.eslintrc.js
/** @type {import('eslint').ESLint.ConfigData} */
module.exports = {
  "extends": [
    "eslint:recommended",
    "plugin:@typescript-eslint/recommended",  // (A)
    "plugin:@typescript-eslint/recommended-requiring-type-checking",  // (B)
    "next/core-web-vitals",
    "prettier"
  ],
  "parser": "@typescript-eslint/parser",
  "parserOptions":  {
    "project": "./tsconfig.json"
  },
  "plugins": [
    "@typescript-eslint"
  ],
  "root": true
}  

公式で推奨している 設定(A) を加えます。
https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/eslint-plugin/src/configs/recommended.ts#L5-L36

なお、(A)の中では以下も読み込んでいて、
plugin:@typescript-eslint/recommended と機能がバッティングしている eslint:recommended の設定を切ってくれています。

https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/eslint-plugin/src/configs/eslint-recommended.ts#L6-L35

型についてのルールを追加した 設定(B) も追加して、より厳密にします。
https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/eslint-plugin/src/configs/recommended-requiring-type-checking.ts

参考

https://typescript-eslint.io/getting-started
https://typescript-eslint.io/linting/typed-linting
https://typescript-eslint.io/linting/configs

(5)あるといいプラグインを追加する

prettier-plugin-tailwindcss

tailwindcssを使っている場合は、是非いれましょう。
cssのclassの並び替えを自動でやってくれます。

https://github.com/tailwindlabs/prettier-plugin-tailwindcss

npm install -D prettier-plugin-tailwindcss  

https://github.com/tailwindlabs/prettier-plugin-tailwindcss#compatibility-with-other-prettier-plugins:~:text=This plugin follows,Prettier config explicitly%3A

インストールするだけで自動ロードする環境としない環境があるようです(yarn, pnpm)。
その場合は明示的な設定が必要と書いてあるのでpluginsに追加しましょう。
他のプラグインを入れてカスタマイズ出来るみたいですが、デフォルトのままでも十分活躍してくれます。

2023/10/16 編集
pluginsの箇所で、require使うと 新しいバージョンだとエラーになるのを修正

https://github.com/tailwindlabs/prettier-plugin-tailwindcss/issues/207#issuecomment-1697777343

prettier.config.js
/** @type {import('prettier').Config} */
module.exports = {
+ plugins: ['prettier-plugin-tailwindcss'],
  "semi": false,
  "singleQuote": true, 
  "printWidth": 90, 
  "tabWidth": 2,
  "trailingComma": "all",
  "jsxSingleQuote": true
}

https://github.com/tailwindlabs/prettier-plugin-tailwindcss/issues/207#issuecomment-1697777343

Tailwind CSS IntelliSense

拡張機能を入れれば、保存時に並び替え、classの補完、不正な値や重複に警告を出してくれます。

https://marketplace.visualstudio.com/items?itemName=bradlc.vscode-tailwindcss

recommendationsにも追加しときましょう。

.vscode/extensions.json
{
  "recommendations": [
    "dbaeumer.vscode-eslint",
    "esbenp.prettier-vscode",
+   "bradlc.vscode-tailwindcss"
   ]
}

ちなみにvscodeのclass補完が遅い場合はこの設定で早くなりました

.vscode/settings.json
{
 ~
 ~ 
+   "editor.quickSuggestions": {
+    "strings": true
+  }
}

eslint-plugin-import

importとexportの部分のルールを設定して並び替えも行ってくれます。
importが増えてくると、ファイル上部がカオスなことになるので、こちらの設定もおすすめします。

https://github.com/import-js/eslint-plugin-import

npm install eslint-plugin-import --save-dev

モジュールの種類や、パスの種類で順番についてのルールを設定出来ます。
lint時や、エディタで設定しておけば保存時にも整形してくれます。
これは手動でやるとなかなか大変です。

設定の詳細は、公式リポジトリか参考記事をご確認ください。

.eslintrc.js
/** @type {import('eslint').ESLint.ConfigData} */
module.exports = {
     〜
     plugins: ['import', '@typescript-eslint'],
    〜
  〜
  rules: {
    'import/order': [
      'error',
      {
        groups: [
          'builtin',
          'external',
          'internal',
          ['parent', 'sibling'],
          'object',
          'type',
          'index',
        ],
        'newlines-between': 'always',
        pathGroupsExcludedImportTypes: ['builtin'],
        pathGroups: [
          {
            pattern: '@/utils/**',
            group: 'internal',
            position: 'before',
          },
          {
            pattern: '@/libs/**',
            group: 'internal',
            position: 'before',
          },
          {
            pattern: '@/hooks/**',
            group: 'internal',
            position: 'before',
          },
          {
            pattern: '@/components/**',
            group: 'internal',
            position: 'before',
          },
          {
            pattern: '@/const/**',
            group: 'internal',
            position: 'before',
          },
          {
            pattern: '@/types/**',
            group: 'internal',
            position: 'before',
          },
        ],
        alphabetize: {
          order: 'asc',
        },
      },
    ],
  },
}
       

参考

https://zenn.dev/rena_h/scraps/fd330154d02f76

commit時にコードの検証をする

最後に、commit時にコードの検証と整形を自動で行う設定をします。

lint-staged

lint-stagedは、Gitでステージングされたファイルに対してリント(コードの品質とスタイルチェック)を実行するツールです。

https://www.npmjs.com/package/lint-staged?activeTab=readme

huskyは

huskyは、Gitフックを簡単に設定し、特定のGitイベント(コミットやプッシュなど)が発生したときに自動的にタスクを実行できるようにするツールです。

https://www.npmjs.com/package/husky?ref=hackernoon.com

処理の流れ

1. git commit
2. huskyがgitのイベントを検知
3. lint-staged が呼ばれ検証と整形を行う

設定

huskyの設定

こちらを参考にすすめます。
https://prettier.io/docs/en/install.html#git-hooks

npm install --save-dev husky           

husky のインストール

npm install --save-dev lint-staged

lint-staged のインストール

npx husky install                                 

このコマンドでルートディレクトリに .huskyディレクトリが出来ます。

npm pkg set scripts.prepare="husky install"       

package.jsonに husky installを追加します。
npm installが実行された場合、prepareスクリプトは自動的に実行されます。

npx husky add .husky/pre-commit "npx lint-staged"

Gitのpre-commitフックにlint-stagedを追加するためのHusky設定を行っています。
以下のファイルが自動で作成されて、commit時に lint-stagedが走ります。

.husky/pre-commit
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"

npx lint-staged

lint-stagedの設定

通常のlint-stagedの設定ではnextjsでlintを設定するとエラーが起きてしまいます。

package.json
{
  "lint-staged": {
    "*": "your-cmd"
  }
}

nextjs公式の設定の仕方があるので、そちらを参照して設定します。

touch .lintstagedrc.js

https://nextjs.org/docs/basic-features/eslint#lint-staged

.lintstagedrc.js
const path = require('path')

const buildEslintCommand = (filenames) =>
  `next lint --fix --file ${filenames
    .map((f) => path.relative(process.cwd(), f))
    .join(' --file ')}`

module.exports = {
  '*.{ts,tsx}': [buildEslintCommand],
}

さらにprettierの設定を追加します

.lintstagedrc.js

const path = require('path')

const buildEslintCommand = (filenames) =>
  `next lint --fix --file ${filenames
    .map((f) => path.relative(process.cwd(), f))
    .join(' --file ')}`

module.exports = {
  '*.{ts,tsx}': [
   buildEslintCommand,
+  "prettier --write"
  ]
}

上記の設定をすると
{.ts,.tsx}ファイルに対してnext lintと prettier
をコミット時に行ってくれます。

これで完成!!と言いたい所ですが、まだ抜け穴があります。
型エラー(ts2322)などは拾ってくれません。
これはeslintのエラーではなく、tsのコンパイルエラーだからです。

このまま、push→ビルド→コケてエラーを知る、というのはよくあると思います。

なので、tscのコンパイルの検証の設定を入れます。

.lintstagedrc.js
const path = require('path')

const buildEslintCommand = (filenames) =>
  `next lint --fix --file ${filenames
    .map((f) => path.relative(process.cwd(), f))
    .join(' --file ')}`

module.exports = {
  '*.{ts,tsx}': [
+   () => 'tsc --incremental false --noEmit',
    buildEslintCommand,
    'prettier --write',
  ],
}

  • --incrementalオプションは入っているとコンパイラは前回のビルドから変更されたファイルのみをコンパイルし、ビルド速度を大幅に向上させることができるオプションですが、入っているとルートディレクトリに キャッシュ用のtsconfig.tsbuildinfoファイルが出来てしまうので、切ります。

  • --noEmitオプションは、コンパイラにコードの型チェックを実行するように指示しますが、ファイルの出力は行いません。 今回は検証だけ行いたいので、設定します。

これで、ts特有のエラーの時は、エラーを出るようになりました。

commit時にそれぞれの処理が走り、問題無ければ commitのコメント入力へ移動します。

SourcetreeのGUIからcommitを行うとエラーになってしまうので、
ターミナルのコマンドからcommitするようにしてください。
https://www.sourcetreeapp.com/

最後に

1から設定を見直して、とても勉強になりました。
この記事を読んで、開発環境の設定を見直すきっかけになれば幸いです。

Discussion