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つだけだ。

  1. .eslintrc.* と .eslintignore は読まれない。除外は globalIgnores()(または ignores だけを持つオブジェクト)で書く
  2. extends は defineConfig() を使う場合のみ、各設定オブジェクトの中で使える(v9.22.0 で追加)
  3. 後ろに書いた設定ほど優先される。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)も合わせて参照してほしい。

技術ブログの記事一覧へ