ESLintのflat configとは、プロジェクト直下の eslint.config.js に、設定オブジェクトの配列をJavaScriptで書く設定方式です。 ESLint 9で標準になり、2026年2月リリースのESLint 10では従来の .eslintrc.* 形式が使えなくなりました。 現在ESLintを使うなら、flat configが唯一の設定方法です。

この記事では、ESLint 10.11.0 + typescript-eslint 8.71.0 で、JavaScript・TypeScript・React・Vue・Node.js・Prettier併用の用途別の完成形 eslint.config.js を載せています。すべて npx eslint を実行し、検出結果、--fix の前後、.eslintrc からの自動移行の結果を掲載しました。ignore が効かないケースなどのエラーも再現しています。

用途別の早見表

用途 追加で入れるパッケージ 設定のポイント
JavaScript(ブラウザ) eslint @eslint/js globals extends: [js.configs.recommended]、globals.browser
TypeScript typescript-eslint typescript tseslint.configs.recommended
TypeScript(型情報つき) 同上 recommendedTypeChecked + parserOptions.projectService: true
React @eslint-react/eslint-plugin eslintReact.configs['recommended-typescript']
Vue eslint-plugin-vue pluginVue.configs['flat/recommended'] + parserOptions.parser
Node.js(ESM) eslint-plugin-n nodePlugin.configs['flat/recommended-module']、globals.nodeBuiltin
Prettier と併用 eslint-config-prettier prettier eslint-config-prettier/flat を配列の最後に置く
ファイルを除外 (なし) globalIgnores(['dist/'])
設定の中身を確認 @eslint/config-inspector npx eslint --inspect-config
eslintrc から移行 @eslint/migrate-config npx @eslint/migrate-config .eslintrc.json

設定オブジェクトのキー一覧

キー 役割 例
files 対象ファイルのglob ['**/*.{ts,tsx}']
ignores このオブジェクトから除外(単独なら全体から除外) ['**/*.test.ts']
extends 他の設定を継承(defineConfig 内で使える) [js.configs.recommended]
plugins プラグインを名前つきで登録 { react: reactPlugin }
rules ルールの有効・無効 { eqeqeq: ['error', 'always'] }
languageOptions.globals グローバル変数 globals.browser
languageOptions.parserOptions パーサーの設定 { projectService: true }
languageOptions.sourceType module / commonjs / script 既定は .js が module、.cjs が commonjs

本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。

(記載例)出典:株式会社RJC「ESLint flat config設定|v10対応の完成形コピペ集」

検証環境とESLint 10の主な変更点

パッケージ バージョン
Node.js 22.22.2
eslint 10.11.0
typescript-eslint 8.71.0
typescript 6.0.3(理由は後述)
eslint-plugin-vue 10.11.1
@eslint-react/eslint-plugin 5.22.1

公式の移行ガイド(Migrate to v10.x)で確認した、設定に関わる変更です。

変更 内容
eslintrc 形式の削除 .eslintrc.* は読み込まれない
設定ファイルの探し方 リントするファイルのディレクトリから上に向かって eslint.config.* を探す
/* eslint-env */ コメント エラーとして報告される
必要な Node.js v20.19.0以上 / v22.13.0以上 / v24以上
eslint:recommended no-unassigned-vars no-useless-assignment preserve-caught-error が追加

TypeScript 7(latest は 7.0.2)について:typescript-eslint 8.71.0 の peerDependencies は typescript: '>=4.8.4 <6.1.0' で、TypeScript 7.0.2 と一緒に入れると npm error ERESOLVE unable to resolve dependency tree(Found: typescript@7.0.2)になりました。

2026年9月時点では、ESLintと組み合わせるなら typescript@6.0.x を入れると依存関係が解決します(この記事の検証も 6.0.3 です)。対応状況は typescript-eslint のリリースノートで確認してください。

JavaScript の完成形

npm i -D eslint @eslint/js globals
// eslint.config.js(package.json に "type": "module")
import { defineConfig, globalIgnores } from 'eslint/config'
import js from '@eslint/js'
import globals from 'globals'

export default defineConfig([
  globalIgnores(['dist/']),
  {
    files: ['**/*.{js,mjs,cjs}'],
    plugins: { js },
    extends: ['js/recommended'],
    languageOptions: { globals: globals.browser },
    rules: {
      'prefer-const': 'error',
      'no-var': 'error',
      eqeqeq: ['error', 'always'],
    },
  },
])

defineConfig と globalIgnores は eslint/config から読み込みます。 defineConfig を使うと extends が書け、配列の入れ子も平らにしてくれます。'js/recommended' は plugins に登録した js の recommended 設定を指す文字列で、js.configs.recommended と直接書いても同じです。

実行ログ

リント対象のコードです。

// src/order.js
var total = 0
const unused = 1

export function addItem(items, item) {
  if (item.qty == 0) {
    return items
  }
  let result = [...items, item]
  return result
}

console.log(undeclaredVar)
$ npx eslint .

src/order.js
   1:1   error  Unexpected var, use let or const instead           no-var
   1:5   error  'total' is assigned a value but never used         no-unused-vars
   2:7   error  'unused' is assigned a value but never used        no-unused-vars
   5:16  error  Expected '===' and instead saw '=='                eqeqeq
   8:7   error  'result' is never reassigned. Use 'const' instead  prefer-const
  12:13  error  'undeclaredVar' is not defined                     no-undef

✖ 6 problems (6 errors, 0 warnings)
  2 errors and 0 warnings potentially fixable with the `--fix` option.

(ファイルパスは短く表記しています。以降も同様)

--fix の前後

npx eslint . --fix で自動修正された差分です。

- var total = 0
+ const total = 0
...
-   let result = [...items, item]
+   const result = [...items, item]

自動修正されたのは no-var と prefer-const の2件で、残る4件は人が判断して直します。

TypeScript の完成形(型情報つき)

npm i -D eslint @eslint/js globals typescript-eslint typescript@6
// eslint.config.js
import { defineConfig, globalIgnores } from 'eslint/config'
import js from '@eslint/js'
import tseslint from 'typescript-eslint'
import globals from 'globals'

export default defineConfig([
  globalIgnores(['dist/']),
  {
    files: ['**/*.{js,mjs,cjs,ts,mts,cts}'],
    extends: [js.configs.recommended, tseslint.configs.recommendedTypeChecked],
    languageOptions: {
      globals: globals.browser,
      parserOptions: {
        projectService: true,
        tsconfigRootDir: import.meta.dirname,
      },
    },
  },
  {
    // 型情報を使うルールを JS ファイルでは無効にする
    files: ['**/*.{js,mjs,cjs}'],
    extends: [tseslint.configs.disableTypeChecked],
  },
])

projectService: true は、各ファイルに対応する tsconfig.json を自動で見つけて型情報を使う設定です。以前の project: './tsconfig.json' より設定が簡単で、typescript-eslint が推奨しています。型情報が不要なら tseslint.configs.recommended にして parserOptions を省けます(速く動きます)。

実行ログ

// src/task.ts
type Task = { id: number; title: string; done: boolean }

export async function fetchTask(id: number): Promise<Task> {
  const res = await fetch(`/api/tasks/${id}`)
  return res.json()
}

export function load(id: number) {
  fetchTask(id)               // await し忘れ
  const data: any = { id }
  return data.title as string
}
src/task.ts
   5:3   error  Unsafe return of a value of type `Promise<any>`   @typescript-eslint/no-unsafe-return
   9:3   error  Promises must be awaited, end with a call to .catch, end with a call to .then with a rejection handler or be explicitly marked as ignored with the `void` operator  @typescript-eslint/no-floating-promises
  10:15  error  Unexpected any. Specify a different type           @typescript-eslint/no-explicit-any
  11:15  error  Unsafe member access .title on an `any` value      @typescript-eslint/no-unsafe-member-access

await の付け忘れ(no-floating-promises)や any の素通しは、型情報つきルールで検出できます。 tsc では通るコードでも見つけられるのが利点です。

React の完成形

eslint-plugin-react@7.37.5 は peerDependencies が eslint 9.7 までで、ESLint 10 と入れると ERESOLVE になりました。 ESLint 10 では flat config 前提の @eslint-react/eslint-plugin が入れやすいです。

npm i -D eslint @eslint/js globals typescript-eslint typescript@6 @eslint-react/eslint-plugin
// eslint.config.js
import { defineConfig, globalIgnores } from 'eslint/config'
import js from '@eslint/js'
import tseslint from 'typescript-eslint'
import eslintReact from '@eslint-react/eslint-plugin'
import globals from 'globals'

export default defineConfig([
  globalIgnores(['dist/']),
  {
    files: ['**/*.{ts,tsx}'],
    extends: [
      js.configs.recommended,
      tseslint.configs.recommended,
      eslintReact.configs['recommended-typescript'],
    ],
    languageOptions: { globals: globals.browser },
  },
])

実行ログ

// TaskList.tsx
import { useEffect, useState } from 'react'

type Task = { id: number; title: string }

export function TaskList({ tasks }: { tasks: Task[] }) {
  const [count, setCount] = useState(0)
  if (tasks.length === 0) {
    const [msg] = useState('タスクなし')   // 条件分岐の中でフック
    return <p>{msg}</p>
  }
  useEffect(() => {
    setCount(tasks.length)
  }, [])
  return (
    <ul>
      {tasks.map((t) => <li>{t.title}</li>)}
      <li>{count}</li>
    </ul>
  )
}
8:19  error    @eslint-react/rules-of-hooks - React Hook "useState" is called conditionally. …
11:3  error    @eslint-react/rules-of-hooks - React Hook "useEffect" is called conditionally. …
12:5  warning  @eslint-react/set-state-in-effect - Do not call the 'set' function 'setCount' of 'useState' synchronously in an effect. …
13:6  warning  @eslint-react/exhaustive-deps - React Hook useEffect has a missing dependency: 'tasks.length'. …
16:25 error    @eslint-react/no-missing-key - Missing 'key' for element when rendering list.

(読みやすいように JSON 出力から整形しています)

@eslint-react 5.x には rules-of-hooks と exhaustive-deps が含まれています。 eslint-plugin-react-hooks@7.1.1 の configs.flat.recommended も一緒に入れたところ、同じ指摘が react-hooks/... と @eslint-react/... で2回ずつ表示されました。どちらか一方にそろえると結果が見やすくなります。

Vue の完成形

npm i -D eslint @eslint/js globals typescript-eslint typescript@6 eslint-plugin-vue
// eslint.config.js
import { defineConfig, globalIgnores } from 'eslint/config'
import js from '@eslint/js'
import tseslint from 'typescript-eslint'
import pluginVue from 'eslint-plugin-vue'
import globals from 'globals'

export default defineConfig([
  globalIgnores(['dist/']),
  {
    files: ['**/*.{js,ts,vue}'],
    extends: [
      js.configs.recommended,
      tseslint.configs.recommended,
      pluginVue.configs['flat/recommended'],
    ],
    languageOptions: {
      globals: globals.browser,
      parserOptions: { parser: tseslint.parser }, // <script lang="ts"> を読む
    },
  },
])

.vue 全体は vue-eslint-parser、<script lang="ts"> の中は parserOptions.parser に渡したパーサーが読みます。 flat config 用の設定名は flat/recommended のように flat/ 付きです。

実行ログと --fix の前後

<!-- ItemList.vue -->
<script setup lang="ts">
import { ref } from 'vue'
const props = defineProps<{ items: { id: number; name: string }[] }>()
const keyword = ref('')
const unusedVar: any = 1
</script>

<template>
  <input v-model="keyword">
  <ul>
    <li v-for="item in props.items" v-if="item.name.includes(keyword)">{{ item.name }}</li>
  </ul>
</template>
5:7   error    @typescript-eslint/no-unused-vars - 'unusedVar' is assigned a value but never used.
5:18  error    @typescript-eslint/no-explicit-any - Unexpected any. Specify a different type.
11:5  error    vue/require-v-for-key - Elements in iteration expect to have 'v-bind:key' directives.
11:37 warning  vue/max-attributes-per-line - 'v-if' should be on a new line.
11:37 error    vue/no-use-v-if-with-v-for - The 'props.items' expression inside 'v-for' directive should be replaced with a …
11:72 warning  vue/singleline-html-element-content-newline - Expected 1 line break after opening tag (`<li>`), but no line breaks found.
11:87 warning  vue/singleline-html-element-content-newline - Expected 1 line break before closing tag (`</li>`), but no line breaks found.

--fix 後は7件が4件になり、テンプレートは次のように整形されました。

-     <li v-for="item in props.items" v-if="item.name.includes(keyword)">{{ item.name }}</li>
+     <li
+       v-for="item in props.items"
+       v-if="item.name.includes(keyword)"
+     >
+       {{ item.name }}
+     </li>

自動で直るのは見た目(改行・属性の配置)の3件で、v-for と v-if の同時使用や key の付け忘れは、コードの意図に合わせて人が直します。

Prettier と併用する完成形

整形は Prettier、問題検出は ESLint と分けるのが一般的です。eslint-config-prettier はPrettierとぶつかるルールをオフにします。

npm i -D prettier eslint-config-prettier
// eslint.config.js(Vue の完成形に追加)
import eslintConfigPrettier from 'eslint-config-prettier/flat'

export default defineConfig([
  // ...ここまでの設定...
  eslintConfigPrettier, // 最後に置いて整形系ルールをオフ
])

同じ ItemList.vue(修正前)を再度リントすると、整形系の警告3件(vue/max-attributes-per-line など)が消え、コードの問題4件(error)だけが残りました。 整形は npx prettier --check .(確認)と --write(修正)で行います。eslintConfigPrettier を配列の途中に置くと、後ろの設定で整形系ルールが再び有効になるため、必ず最後に置きます。

npx eslint-config-prettier vuex/ItemList.vue を実行すると No rules that are unnecessary or conflict with Prettier were found. と表示され、ぶつかるルールが残っていないことを確認できます。

Node.js の完成形

npm i -D eslint @eslint/js globals eslint-plugin-n
// eslint.config.js
import { defineConfig, globalIgnores } from 'eslint/config'
import js from '@eslint/js'
import nodePlugin from 'eslint-plugin-n'
import globals from 'globals'

export default defineConfig([
  globalIgnores(['dist/']),
  {
    files: ['**/*.{js,mjs}'],
    extends: [js.configs.recommended, nodePlugin.configs['flat/recommended-module']],
    languageOptions: { globals: globals.nodeBuiltin },
  },
])
// server.js
import { readFile } from 'node:fs/promises'
import { parse } from 'some-missing-lib'

const data = await readFile(new URL('./stock.json', import.meta.url), 'utf8')
console.log(parse(data), process.env.PORT, __dirname)
2:23  error  n/no-missing-import - Can't resolve 'some-missing-lib' in '…/node'
5:44  error  no-undef - '__dirname' is not defined.

ESMのプロジェクトでは globals.node ではなく globals.nodeBuiltin を使うと、ESMでは使えない __dirname を検出できます。 globals.node にした場合、__dirname の指摘は出ませんでした(CommonJS用のグローバルも含むため)。

設定の中身を確認する(config inspector)

npx eslint --inspect-config          # ESLint 本体から起動
npx @eslint/config-inspector --port 7316

ブラウザで設定の一覧が開き(v3.5.0で確認)、どの設定がどのファイルにどの順で効いているかを確認できます。JavaScript の完成形では、既定の **/node_modules/ 除外、globalIgnores の dist/、js/recommended などの7項目が並びました。コマンドだけなら npx eslint --print-config src/order.js で最終的なルールをJSONで出せます。

eslintrc から移行する(@eslint/migrate-config)

ESLint 10で .eslintrc.json だけのプロジェクトを実行すると、次のエラーになります。

ESLint couldn't find an eslint.config.* file.

From ESLint v9.0.0, the default configuration file is now eslint.config.*.

移行元の .eslintrc.json です。

{
  "root": true,
  "env": { "browser": true, "es2022": true },
  "parserOptions": { "ecmaVersion": "latest", "sourceType": "module" },
  "extends": ["eslint:recommended"],
  "ignorePatterns": ["dist/"],
  "rules": { "no-var": "error", "prefer-const": "error", "eqeqeq": ["error", "always"] }
}
$ npx @eslint/migrate-config .eslintrc.json

Wrote new config to ./eslint.config.mjs

You will need to install the following packages to use the new config:
- globals
- @eslint/js
- @eslint/eslintrc

生成された eslint.config.mjs です(空行のみ詰めています)。

import { defineConfig, globalIgnores } from "eslint/config";
import globals from "globals";
import path from "node:path";
import { fileURLToPath } from "node:url";
import js from "@eslint/js";
import { FlatCompat } from "@eslint/eslintrc";

const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const compat = new FlatCompat({
    baseDirectory: __dirname,
    recommendedConfig: js.configs.recommended,
    allConfig: js.configs.all
});

export default defineConfig([globalIgnores(["**/dist/"]), {
    extends: compat.extends("eslint:recommended"),
    languageOptions: {
        globals: {
            ...globals.browser,
        },

        ecmaVersion: "latest",
        sourceType: "module",
    },
    rules: {
        "no-var": "error",
        "prefer-const": "error",
        eqeqeq: ["error", "always"],
    },
}]);

env は globals、ignorePatterns は globalIgnores に自動で置き換わりました。 "eslint:recommended" は互換レイヤー(FlatCompat)経由になっているので、js.configs.recommended に置き換えると @eslint/eslintrc が不要になります。

整理後は、FlatCompat 関連の import と compat を削除し、files: ['**/*.{js,mjs,cjs}'] と extends: [js.configs.recommended] を書いた形になります(上の「JavaScript の完成形」と同じ構成です)。

自動生成版と整理版のどちらでも、同じ src/order.js に対して6件の同じエラーが検出されることを確認しました。 ecmaVersion: 'latest' と sourceType: 'module' は flat config の既定値なので省略できます。

その他の対応は、plugins: ['react'](文字列)→ plugins: { react: reactPlugin }(importしたオブジェクト)、overrides → 配列に { files, rules } を追加、です。

よくあるエラーと対処

ignore が効かない

// dist/ が除外されない書き方
export default defineConfig([
  js.configs.recommended,
  {
    ignores: ['dist/'],          // ← 他のキーと同じオブジェクトに書いている
    languageOptions: { globals: globals.browser },
    rules: { 'no-var': 'error' },
  },
])
dist/bundle.js
  1:7  error  'x' is assigned a value but never used  no-unused-vars

ignores は、他のキーと同じオブジェクトに書くと「そのオブジェクトの設定を適用しないファイル」という意味になり、全体の除外にはなりません。 globalIgnores(['dist/']) を使うと、明示的に指定しても次のように除外されます。

$ npx eslint dist/bundle.js
dist/bundle.js
  0:0  warning  File ignored because of a matching ignore pattern. Use "--no-ignore" to disable file ignore settings or use "--no-warn-ignored" to suppress this warning

型情報を使うルールで parserOptions がない

Error: Error while loading rule '@typescript-eslint/await-thenable': You have used a rule which requires type information, but don't have parserOptions set to generate type information for this file. See https://tseslint.com/typed-linting for enabling linting with type information.

recommendedTypeChecked を使うときは parserOptions.projectService: true と tsconfigRootDir を設定します。tsconfig の include に入っていないファイルでは、次のエラーになりました。

scripts/tool.ts
  0:0  error  Parsing error: scripts/tool.ts was not found by the project service. Consider either including it in the tsconfig.json or including it in allowDefaultProject

tsconfig の include に追加するか、設定ファイルなど少数のファイルなら projectService: { allowDefaultProject: ['*.js'] } で許可します。

その他のエラー早見表

エラー文・症状 原因 対処
Cannot find package 'eslint-plugin-vue' imported from …/eslint.config.js プラグイン未インストール npm i -D eslint-plugin-vue
ESLint couldn't find an eslint.config.* file. eslintrc のみ @eslint/migrate-config で移行
ERESOLVE … peer typescript@">=4.8.4 <6.1.0" TypeScript 7 と typescript-eslint 8 typescript@6 を入れる
同じ指摘が2回出る 同じルールを持つプラグインを両方有効化 どちらかに統一
下の階層だけ設定が変わる v10 は最も近い eslint.config.* を使う 設定ファイルを1つにまとめる

最後の行は、node/ に no-console だけの eslint.config.js を置いて確認しました。ルート側の設定は使われず no-console のみが報告され、最も近い設定ファイルだけが使われる(上の階層と合成されない)ことがわかります。

よくある質問

Q. eslint.config.js と eslint.config.mjs のどちらにすればよいですか? A. package.json に "type": "module" があれば .js で import 構文が使えます。ない場合は .mjs にします。TypeScriptで書く eslint.config.ts も使えます(jiti 2.2.0以上が必要)。

Q. tseslint.config() と defineConfig() はどちらを使えばよいですか? A. ESLint 本体の defineConfig(eslint/config)で extends が書けるので、この記事のように defineConfig の中で typescript-eslint の設定を使えます。tseslint.config() も引き続き提供されています。

Q. ESLint 9 の flat config は、10 でも書き換えずに動きますか? A. flat config の書き方は同じです。v10 で変わったのは、/* eslint-env */ コメントがエラーになる点、設定ファイルを対象ファイルの場所から探す点、eslint:recommended へのルール追加などです。上げたあとに npx eslint . で新しい指摘を確認します。

まとめ

  • ESLint 10 では eslint.config.*(flat config)が唯一の設定方式
  • defineConfig + extends + globalIgnores の3点で、読みやすい設定が書ける
  • 型情報つきルールは projectService: true で有効化。await 忘れなどを検出できた
  • ESLint 10 と組み合わせるなら React は @eslint-react、TypeScript は 6.0.x(2026年9月時点の peer 範囲)
  • Prettier 併用は eslint-config-prettier/flat を最後に置き、整形系の警告3件が消えることを確認
  • ignores は単独オブジェクトか globalIgnores で書くと全体から除外される
  • 移行は @eslint/migrate-config → FlatCompat を外して整理の2段階で進めると確実

本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。

(記載例)出典:株式会社RJC「ESLint flat config設定|v10対応の完成形コピペ集」

株式会社RJCでは、リントや開発環境の設定を実際に動かして確かめ、チームで使いやすい形をプロジェクトの中で仲間と学び合っています。こうした知識を共有しながら、日々技術を磨いています。

株式会社RJC ― SI事業・SES事業・AI駆動開発。RJCは一緒に成長を楽しめる会社です。

WE ARE HIRING

RJCで一緒に開発しながら、
成長を楽しみませんか?

RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!

  • 127日年間休日
  • 12時間平均残業時間
  • 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!

ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。

ABOUT RJC RJCがどんな会社か知る 考え方、研修、働き方、福利厚生、社員の前職まで。RJCのことが丸わかり! RJC丸わかりページへ JOB DESCRIPTION 仕事内容・待遇を見てみる 仕事内容、給与・待遇、選考の流れ。経験者も未経験も!応募前に知りたいこと、まとめました! 募集要項を見る ENTRY エントリーする エントリーは1〜2分・履歴書不要です。まずは話を聞いてみたい、という方でも歓迎です! エントリーフォームへ

RJCで一緒に開発しながら、 成長を楽しみませんか?