ESLint v9 eslint.config.js の書き方と動かない原因【flat config・fixupPluginRules・v10対応】
公開日:2026-04-13/カテゴリ:ESLint・TypeScript・フロントエンド/AnchorUp 技術ブログ
ESLint v9.0がリリースされ、flat config(eslint.config.js)がデフォルト設定形式になった。これまでの.eslintrc.jsはv9ではESLINT_USE_FLAT_CONFIG=false環境変数で一時的に使えたが、2026年2月リリースの ESLint v10 で eslintrc 形式は完全に削除された。さらに ESLint v9 系は 2026-08-06 に EOL(サポート終了) を迎えており、今から設定を書くなら flat config 一択だ。
いざ移行しようとすると「プラグインが読み込めない」「ルールが効かない」「TypeScriptのパースエラー」など、じわじわとハマりポイントが出てくる。実際のプロジェクトで踏んだ5つの罠と対処法を解説する。
結論: eslint.config.js はどう書く?
ESLint v9 以降は、プロジェクトルートに eslint.config.js(または .mjs / .cjs / .ts)を置き、設定オブジェクトの配列を export する。 ESLint v9.22.0 以降なら、eslint/config の defineConfig() と globalIgnores() を使うのが現在の公式推奨の書き方だ。
// eslint.config.js(ESLint v9.22+ / v10)
import { defineConfig, globalIgnores } from 'eslint/config'
import js from '@eslint/js'
import tseslint from 'typescript-eslint'
import prettier from 'eslint-config-prettier/flat'
export default defineConfig([
globalIgnores(['dist', 'build', 'coverage']),
{
files: ['**/*.{js,mjs,ts,tsx}'],
plugins: { js },
extends: ['js/recommended'],
},
tseslint.configs.recommended,
{
rules: {
'no-console': 'warn',
},
},
prettier, // Prettier と競合するルールを無効化するので必ず最後
])
ポイントは3つだけだ。
.eslintrc.*と.eslintignoreは読まれない。除外はglobalIgnores()(またはignoresだけを持つオブジェクト)で書くextendsはdefineConfig()を使う場合のみ、各設定オブジェクトの中で使える(v9.22.0 で追加)- 後ろに書いた設定ほど優先される。Prettier 無効化は最後に置く
ESLint v9 と v10 で何が変わった?(2026年10月時点の最新状況)
結論: 現在の最新メジャーは ESLint v10(2026-02 リリース)で、eslintrc 形式は完全に使えなくなった。v9 系は 2026-08-06 で EOL。 主な違いは次のとおり。
| 項目 | ESLint v8 | ESLint v9 | ESLint v10 |
|---|---|---|---|
| デフォルト設定形式 | .eslintrc.* |
eslint.config.js(flat config) |
eslint.config.js のみ |
.eslintrc.* |
標準 | 非推奨(ESLINT_USE_FLAT_CONFIG=false で使用可) |
削除 |
ESLINT_USE_FLAT_CONFIG |
true で flat config を試用 |
false で eslintrc に戻せる |
効果なし |
| 設定ファイルの探索 | 対象ファイルから上位へ | カレントディレクトリ基準(新方式はオプション) | 対象ファイルのディレクトリから上位へ探索 |
| 対応 Node.js | 12.22+ 等 | 18.18.0+ / 20.9.0+ / 21+ | 20.19.0+ / 22.13.0+ / 24+ |
| サポート状況 | EOL(2024-10-05) | EOL(2026-08-06) | 現行 |
v10 では eslint:recommended に no-unassigned-vars / no-useless-assignment / preserve-caught-error の3ルールが追加されたため、v9 → v10 に上げただけで新たなエラーが出ることがある。また /* eslint-env */ コメントはエラーになるので削除が必要だ。
ESLINT_USE_FLAT_CONFIG とは?いつ使う?
ESLINT_USE_FLAT_CONFIG は、flat config と旧 eslintrc 形式のどちらを使うかを切り替える移行期間用の環境変数だ。 ESLint v9 では false を指定すると .eslintrc.* を読む旧動作に戻せたが、ESLint v10 ではこの環境変数自体が機能しない。
# ESLint v9 のみ有効: 一時的に .eslintrc.* を使う
ESLINT_USE_FLAT_CONFIG=false npx eslint src/
# ESLint v10 では無視される → eslint.config.js への移行が必須
v9 で .eslintrc.* のまま実行すると、次のエラーで止まる。これが「ESLint v9 で動かない」の最初の関門だ。
ESLint couldn't find an eslint.config.(js|mjs|cjs) file.
From ESLint v9.0.0, the default configuration file is now eslint.config.js.
If you are using a .eslintrc.* file, please follow the migration guide
to update your configuration file to the new format:
https://eslint.org/docs/latest/use/configure/migration-guide
ESLINT_USE_FLAT_CONFIG=false は移行までの時間稼ぎであり、v9 自体が EOL になった今は恒久対策にならない。CI やエディタ拡張の設定にこの環境変数が残っていると、v10 に上げた途端に「設定が読まれない」原因になるので削除しておこう。
flat configの基本構造をおさらい
従来の.eslintrc.jsとflat configの最大の違いは、グローバルなextendsがなくなり、すべてを配列で組み合わせる点だ。
// 旧: .eslintrc.js
module.exports = {
extends: ['eslint:recommended', 'plugin:@typescript-eslint/recommended'],
parser: '@typescript-eslint/parser',
plugins: ['@typescript-eslint'],
rules: {
'no-console': 'warn',
},
}
// 新: eslint.config.js
import js from '@eslint/js'
import tseslint from 'typescript-eslint'
export default [
js.configs.recommended,
...tseslint.configs.recommended,
{
rules: {
'no-console': 'warn',
},
},
]
一見シンプルだが、この構造の変化がいくつかの罠を生む。
罠1: eslint-plugin-xxx がflat configに未対応でエラーになる
症状
TypeError: Key "plugins": Key "react": Object was not normalized.
または
Error: Plugin "react" was already defined.
原因
flat configはプラグインオブジェクトに新しい正規化要件がある。古いプラグインはflat configが想定するオブジェクト構造に対応していない場合がある。特にeslint-plugin-reactはv7.34以前でこの問題が起きやすい。
対策
プラグインのバージョンを確認し、flat config対応バージョンにアップデートする。
npm ls eslint-plugin-react
# eslint-plugin-react@7.33.x → flat config未対応
# eslint-plugin-react@7.34.x以降 → 対応済み
fixupPluginRules(@eslint/compat)とは?使い方は?
fixupPluginRules は、ESLint v9 のルール API 変更に追従していない古いプラグインを、互換レイヤーで包んで動くようにする関数だ。 ESLint v9 では context.getScope() などのメソッドが context から sourceCode に移動したため、未対応プラグインは実行時に次のようなエラーを出す。
TypeError: context.getScope is not a function
TypeError: context.getAncestors is not a function
TypeError: context.markVariableAsUsed is not a function
TypeError: context.getDeclaredVariables is not a function
まずはプラグインの最新版に上げるのが第一選択。対応バージョンがない場合は@eslint/compatのfixupPluginRulesでラップする:
import { fixupPluginRules } from '@eslint/compat'
import reactPlugin from 'eslint-plugin-react'
export default [
{
plugins: {
react: fixupPluginRules(reactPlugin),
},
rules: {
'react/jsx-uses-react': 'error',
},
},
]
npm install -D @eslint/compat
プラグイン単体ではなく、共有設定(eslint-config-xxx)の中に未対応プラグインが含まれている場合は fixupConfigRules を使う。設定配列内のすべてのプラグインをまとめて fixupPluginRules でラップしてくれる。
import { defineConfig } from 'eslint/config'
import { fixupConfigRules } from '@eslint/compat'
import someConfig from 'eslint-config-some-config'
export default defineConfig([
...fixupConfigRules(someConfig),
{
// プロジェクト固有の設定
},
])
@eslint/compat の README では v9.x / v10.x 両対応とされているので、v10 でも同じ書き方で使える。
罠2: ignoresの書き方が変わり、意図しないファイルがLint対象になる
症状
node_modulesやdistディレクトリがLint対象になり、大量のエラーが出る。または逆に、Lintしたいファイルが除外される。
原因
旧来の.eslintignoreはflat configでは無視される。また、ignoresをconfigオブジェクト内に書くか、単独オブジェクトとして書くかで挙動が異なる。
// NG: これはこのconfigオブジェクトのfilesにのみ適用される「ローカルignore」
export default [
{
files: ['src/**/*.ts'],
ignores: ['src/**/*.test.ts'], // このconfigブロック内だけで有効
rules: { /* ... */ },
},
]
// OK: グローバルignoreにするには、filesを持たない単独オブジェクトにする
export default [
{
ignores: ['dist/**', 'node_modules/**', '**/*.min.js'],
},
{
files: ['src/**/*.ts'],
rules: { /* ... */ },
},
]
対策
グローバルな除外設定は必ず**filesを持たない独立したオブジェクト**として配列の先頭に置く。ESLint v9.22.0 以降なら globalIgnores() を使うと、ローカル ignore と取り違える事故を防げる。
import { defineConfig, globalIgnores } from 'eslint/config'
export default defineConfig([
globalIgnores(['dist/', 'build/', 'coverage/', '**/*.d.ts']),
// ...他の設定
])
従来の書き方(ignores だけを持つオブジェクト)でも同じ意味になる:
// eslint.config.js
import js from '@eslint/js'
import tseslint from 'typescript-eslint'
export default [
// グローバルignoreは必ず先頭に
{
ignores: [
'dist/**',
'build/**',
'coverage/**',
'**/*.d.ts',
],
},
js.configs.recommended,
...tseslint.configs.recommended,
{
files: ['src/**/*.{ts,tsx}'],
rules: {
'no-console': 'warn',
},
},
]
罠3: TypeScriptプロジェクトでparserOptions.projectの設定が別物になった
症状
@typescript-eslintの型情報を使うルール(@typescript-eslint/no-floating-promisesなど)を有効にすると:
Error: You have used a rule which requires type information, but don't have parserOptions set to generate type information for this file.
原因
flat configではparserOptionsはlanguageOptions.parserOptionsに移動した。旧来の書き方では型情報が渡されない。
// NG: 旧来の書き方(flat configでは無効)
export default [
{
parserOptions: { // ← ここに書いても効かない
project: './tsconfig.json',
},
},
]
対策
languageOptionsの下にparserとparserOptionsを移す:
import tseslint from 'typescript-eslint'
import tsParser from '@typescript-eslint/parser'
export default [
...tseslint.configs.recommendedTypeChecked,
{
files: ['src/**/*.{ts,tsx}'],
languageOptions: {
parser: tsParser,
parserOptions: {
project: './tsconfig.json',
tsconfigRootDir: import.meta.dirname, // Node.js 20.11+
},
},
},
]
import.meta.dirnameが使えない環境(Node.js 20.10以前)では:
import { fileURLToPath } from 'url'
import { dirname } from 'path'
const __dirname = dirname(fileURLToPath(import.meta.url))
export default [
{
languageOptions: {
parserOptions: {
project: './tsconfig.json',
tsconfigRootDir: __dirname,
},
},
},
]
罠4: eslint-config-prettierの適用順序でルールが効かなくなる
症状
Prettierと競合するESLintルールを無効化するためにeslint-config-prettierを入れたのに、indentやquotesなどのルールが依然として発火する。または逆に、意図したルールが無効化される。
原因
flat configではextendsの代わりに配列の順序でルールが上書きされる。eslint-config-prettierを途中に挟むと、後続の設定がPrettier無効化より優先されてしまう。
対策
eslint-config-prettierは必ず配列の最後に置く:
import js from '@eslint/js'
import tseslint from 'typescript-eslint'
import prettier from 'eslint-config-prettier'
export default [
js.configs.recommended,
...tseslint.configs.recommended,
{
// プロジェクト固有のルール
rules: {
'no-console': 'warn',
'@typescript-eslint/no-explicit-any': 'error',
},
},
prettier, // ← 必ず最後!Prettierと競合するルールをすべて上書き
]
eslint-config-prettier/flat とは?通常の import との違い
eslint-config-prettier/flat は flat config 用のエントリポイントで、中身は通常版と同じだが name プロパティが付いている。 name があると ESLint Config Inspector(npx @eslint/config-inspector)でどの設定がルールを無効化したか識別しやすくなる。eslint-config-prettier v10.1.1 以降で利用できる。
// flat config ではこちらが推奨
import prettier from 'eslint-config-prettier/flat'
従来の import prettier from 'eslint-config-prettier' も flat config でそのまま動くため、「/flat に変えないとエラーになる」わけではない。どちらでも配列の最後に置くことが重要だ。
なお Prettier との併用自体をやめて Biome に一本化する選択肢もある。移行時のハマりどころは Biome で ESLint・Prettier から移行して動かない時の対処法 にまとめている。
罠5: eslint --initで生成されたconfigがv8形式でv9に通らない
症状
npm init @eslint/config@latestを実行して生成されたeslint.config.jsをそのまま使ったら動いたが、既存の.eslintrc.jsを手動でflat configに変換したら動かない。
Error [ERR_REQUIRE_ESM]: require() of ES Module eslint.config.js not supported.
または
SyntaxError: Unexpected token 'export'
原因
flat configはESモジュール形式で書く必要があるが、プロジェクトが"type": "commonjs"(または未設定)の場合、eslint.config.jsがCJSとして解釈される。
対策
2つの方法がある。
方法A: ファイル拡張子を.mjsにする
mv eslint.config.js eslint.config.mjs
方法B: package.jsonに"type": "module"を追加する
{
"type": "module"
}
ただし方法Bはプロジェクト全体のモジュール形式が変わるため、他のCJS形式のconfigファイル(jest.config.jsなど)も影響を受ける。CJSで書かれたファイルを.cjsにリネームする必要があるケースもある。 ERR_REQUIRE_ESM 自体の仕組みは Node.js の ERR_REQUIRE_ESM エラーの原因と対策 で詳しく解説している。
TypeScript で書きたい場合は eslint.config.ts(.mts / .cts)も使える。ただし jiti パッケージのインストール(v10 では jiti v2.2.0 以上)か、Node.js 側の TypeScript 実行サポートが必要だ。
npm install -D jiti
プロジェクト全体の移行推奨手順:
# 1. flat config移行ツールを使う(旧.eslintrcを自動変換)
npx @eslint/migrate-config .eslintrc.js
# 2. 生成された eslint.config.mjs を確認・調整
# 3. 旧設定ファイルを削除
rm .eslintrc.js .eslintignore
# 4. 動作確認
npx eslint src/ --debug 2>&1 | head -30
まとめ
ESLint v9 flat config移行でよくある罠をまとめると:
| 罠 | 原因 | 対策 |
|---|---|---|
| プラグインがエラー | 旧プラグインが未対応 | バージョンアップ or fixupPluginRules |
| ignoresが効かない | files付きオブジェクト内に書いた |
単独オブジェクトとして先頭に置く |
| 型情報ルールがエラー | parserOptionsの場所が違う |
languageOptions.parserOptionsに移動 |
| Prettierルールが残る | 適用順序が間違い | eslint-config-prettierを最後に |
| ESMエラー | CJSプロジェクトで.jsを使用 |
.mjsにリネーム |
couldn't find an eslint.config |
.eslintrc.* のまま v9 で実行 |
eslint.config.js に移行(v10 は ESLINT_USE_FLAT_CONFIG 無効) |
context.getScope is not a function |
プラグインが v9 ルール API 未対応 | プラグイン更新 or fixupPluginRules |
移行時はnpx @eslint/migrate-configを使って自動変換し、そこからプラグインのバージョンやlanguageOptionsの設定を調整するのが最も確実なアプローチだ。
これから設定を書くなら、ESLint v10 を前提に defineConfig() + globalIgnores() で書いておけば、v9 EOL 後も手戻りがない。公式マイグレーションガイド(v9 / v10)も合わせて参照してほしい。