eslint.config.js の書き方:Flat ConfigでTypeScriptを実務向けに整える
ESLint v9以降の標準、Flat Config(eslint.config.js)の書き方を実例で。typescript-eslint連携、Prettierとの競合回避、CIでの実行まで僕の失敗込みで解説。
「ESLintの設定、コピペしてきたやつでなんとなく動いてる」
僕がずっとそうでした。.eslintrc.json をどこかのテンプレからもらってきて、エラーが出たらルールを off にして黙らせる。中身は読んでいない。それでも回っていたんです。ESLint v9 が来て、設定ファイルが eslint.config.js(Flat Config)に変わるまでは。
ある朝、npm run lint がいきなり全部こけました。eslintrc がもう読まれていない。あわてて「ESLint設定作って」とClaude Codeに丸投げしたら、今度は型チェックが全ファイルに当たって、lintが2分かかるシロモノが返ってきた。賢いはずのAIでも、こちらが何も決めずに頼むと、こうなります。
Flat Config は、慣れると .eslintrc よりずっと素直です。設定が1つのJavaScript配列で、上から順に重なるだけ。今日はその eslint.config.js の書き方を、TypeScript連携・Prettierとの住み分け・CIでの実行まで、僕がつまずいた順に並べていきます。
この記事の要点
- ESLint v9 以降は Flat Config(
eslint.config.js)が標準。設定は「配列が上から重なる」だけのシンプルな構造。 - TypeScript の型を使ったlintは
typescript-eslintのprojectService: trueで有効化。ただし重いのでignoresを最初に書く。 - 整形(インデントや引用符)はPrettierに任せ、ESLintは「実害のあるバグ」に寄せる。競合は
eslint-config-prettierで消す。 - ルールの強さは現場で変える。React管理画面、Astroブログ、ライブラリで「強める所」「緩める所」が違う。
- ローカルで直してもダメ。CIで
npm run lintを固定して初めてチームの品質がそろう。
Flat Configって、何がそんなに違う?
ひとことで言うと、設定が「魔法の文字列」から「ただのJavaScript」になりました。
.eslintrc の時代は、extends: ["airbnb", "plugin:react/recommended"] みたいに文字列を並べて、その文字列がどこの何を指すのかは暗黙のルールでした。プラグインの読み込みも plugins: ["react"] と名前だけ。初見だと、どの設定がどのファイルに効いているのか追えません。
Flat Config はこれを、import した値を配列に並べるだけにしました。
// eslint.config.js のいちばん小さい形
import js from "@eslint/js";
export default [
js.configs.recommended, // ESLint公式のおすすめルール
{
rules: {
"no-unused-vars": "warn", // 未使用の変数は警告
},
},
];
ポイントは3つです。
- 配列が上から順に重なる。後ろの要素ほど強い。あるファイルに複数の要素が当たれば、ルールはマージされます。
- プラグインは
importしてplugins: { ... }に渡す。文字列でどこかから探してくる、という曖昧さがなくなりました。 filesで対象を絞る。files: ["**/*.tsx"]と書けば、その要素はtsxにだけ効きます。書かなければ全ファイルです。
「上から重なる」と「files で絞る」。この2つさえ腹落ちすれば、Flat Config は怖くありません。
まず入れる依存とnpm scripts
設定を書く前に、土台を固定します。ESLint本体、TypeScript連携、React Hooks、アクセシビリティ、import並び替え。Astroを使うときだけ専用プラグインを足します。
npm i -D eslint @eslint/js typescript typescript-eslint globals
npm i -D eslint-plugin-react eslint-plugin-react-hooks eslint-plugin-jsx-a11y
npm i -D eslint-plugin-simple-import-sort
npm i -D eslint-config-prettier
# Astroプロジェクトだけ追加
npm i -D eslint-plugin-astro astro-eslint-parser
eslint-config-prettier を最初から入れているのは、後でPrettierと喧嘩させないためです(理由は後述)。
package.json には、ローカル修正用とCI用を分けて入れます。--max-warnings=0 は「警告もCIで失敗にする」指定。導入初日は厳しすぎるので、エラー数を見てから有効化しても構いません。
{
"scripts": {
"lint": "eslint . --max-warnings=0",
"lint:fix": "eslint . --fix",
"lint:debug": "eslint --print-config src/App.tsx > .eslint-debug.json",
"typecheck": "tsc --noEmit",
"ci:verify": "npm run lint && npm run typecheck"
}
}
lint:debug は地味ですが効きます。「いま src/App.tsx にどのルールが当たっているか」を丸ごと吐き出すコマンドで、設定が想定どおり重なっているかを確認する一時資料になります。
TypeScript/React向けのeslint.config.js(コピペで動く)
ここが本題です。React + TypeScript の一般的なアプリなら、次の eslint.config.js から始めて大丈夫です。そのままコピペできます。
defineConfig は ESLint v9.21 以降で公式が用意したヘルパーで、型補完が効いて書きやすくなります。globalIgnores は「全要素に効く除外」を明示する関数。型情報を使うルールは強力ですが重いので、生成物やビルド出力は必ずここで弾きます。
// eslint.config.js
import js from "@eslint/js";
import { defineConfig, globalIgnores } from "eslint/config";
import globals from "globals";
import react from "eslint-plugin-react";
import reactHooks from "eslint-plugin-react-hooks";
import jsxA11y from "eslint-plugin-jsx-a11y";
import simpleImportSort from "eslint-plugin-simple-import-sort";
import tseslint from "typescript-eslint";
import prettier from "eslint-config-prettier";
export default defineConfig([
// 1) まず全体から除外するパス(型lintが重くなる元を先に消す)
globalIgnores([
"node_modules/",
"dist/",
"build/",
"coverage/",
".next/",
".astro/",
"public/",
"*.min.js",
]),
// 2) 公式のおすすめ + TypeScriptの型ありルール
js.configs.recommended,
tseslint.configs.strictTypeChecked,
tseslint.configs.stylisticTypeChecked,
// 3) 言語設定とグローバル変数。projectServiceで型情報を使う
{
files: ["**/*.{js,mjs,cjs,ts,tsx}"],
languageOptions: {
ecmaVersion: "latest",
sourceType: "module",
globals: { ...globals.browser, ...globals.node, ...globals.es2024 },
parserOptions: {
projectService: true, // TypeScriptに型を問い合わせる
tsconfigRootDir: import.meta.dirname,
},
},
},
// 4) Reactコンポーネントだけに当てる設定
{
files: ["**/*.{jsx,tsx}"],
...react.configs.flat.recommended,
settings: { react: { version: "detect" } },
plugins: { "react-hooks": reactHooks, "jsx-a11y": jsxA11y },
rules: {
...react.configs.flat.recommended.rules,
...reactHooks.configs.recommended.rules,
...jsxA11y.configs.recommended.rules,
"react/react-in-jsx-scope": "off", // React 17+では不要
"react/prop-types": "off", // 型で担保するのでoff
},
},
// 5) 実害のあるミスを拾うルール(ここが心臓部)
{
plugins: { "simple-import-sort": simpleImportSort },
rules: {
"simple-import-sort/imports": "error",
"simple-import-sort/exports": "error",
"@typescript-eslint/consistent-type-imports": [
"error",
{ prefer: "type-imports", fixStyle: "separate-type-imports" },
],
"@typescript-eslint/no-floating-promises": "error", // await忘れ
"@typescript-eslint/no-misused-promises": [
"error",
{ checksVoidReturn: { attributes: false } },
],
"@typescript-eslint/no-unused-vars": [
"error",
{ argsIgnorePattern: "^_", varsIgnorePattern: "^_" },
],
"no-console": ["warn", { allow: ["warn", "error"] }],
},
},
// 6) 設定ファイルなど純JSは型lintを外す(重さと誤検知を回避)
{
files: ["**/*.{js,mjs,cjs}"],
extends: [tseslint.configs.disableTypeChecked],
},
// 7) テストだけ一部のanyを許す
{
files: ["**/*.{test,spec}.{ts,tsx}", "**/__tests__/**/*.{ts,tsx}"],
rules: {
"@typescript-eslint/no-explicit-any": "off",
"@typescript-eslint/no-unsafe-assignment": "off",
"@typescript-eslint/no-unsafe-member-access": "off",
},
},
// 8) いちばん最後にPrettierと競合するルールを全部off(必ず末尾)
prettier,
]);
この設定の狙いは、スタイルの好みより「実害のあるミス」を優先することです。no-floating-promises は await 忘れを拾い、no-misused-promises はReactのイベントハンドラに async 関数をそのまま渡す事故を減らします。consistent-type-imports は型だけのimportを import type に統一して、ビルド結果を読みやすく保ちます。
推奨ルールセットの選び方
typescript-eslint には強さ違いのプリセットが用意されています。最初は迷わず次の表で選んでください。
| プリセット | 性格 | 向いている場面 |
|---|---|---|
recommended | 型情報なし・軽い | まず動かしたい、CIを速く保ちたい |
recommendedTypeChecked | 型情報あり・実害重視 | 一般的なアプリ。最初の本命 |
strictTypeChecked | 型情報あり・厳しめ | ライブラリ、品質を攻めたい |
stylisticTypeChecked | 書き方の一貫性 | 上のどれかに足して使う |
迷ったら recommendedTypeChecked から始めて、慣れたら strictTypeChecked に上げるのが安全です。strict をいきなり既存コードに当てると、初日にエラーが数百出てやる気が折れます(僕は折れました)。
Prettierと喧嘩させない(eslint-config-prettier)
ここ、つまずく人が本当に多いです。
ESLintにも「インデントは2スペース」「セミコロンを付けろ」みたいな整形系のルールがあります。一方でPrettierも同じことをします。両方を素のまま動かすと、ESLintが「セミコロン付けろ」と言い、Prettierが「いや消す」と言って、保存するたびにコードが行ったり来たりする地獄になります。
解決はシンプルで、整形はPrettierに一任し、ESLint側の整形ルールを全部オフにする。それをやってくれるのが eslint-config-prettier です。上のコードの末尾、prettier, がそれです。
// eslint.config.js の最後に置くだけ
import prettier from "eslint-config-prettier";
export default defineConfig([
// ...ここまでに自分のルールを全部書く...
prettier, // 最後。これより後にルールを足すと競合が復活する
]);
注意は1つだけ。prettier は必ず配列のいちばん最後に置きます。Flat Config は上から重なるので、後ろに整形ルールを足すと、せっかく消した競合がよみがえります。
Prettier本体の .prettierrc の中身(行幅やクォートの好み)は、別記事のPrettier設定カスタマイズガイドにまとめました。役割分担はこう覚えてください——ESLintは「バグを止める門番」、Prettierは「見た目を整える清掃係」。掛け持ちさせないのがコツです。
Astroサイトでは.astroを別扱いする
Astroの .astro ファイルは、HTML風テンプレート・TypeScript・コンポーネント呼び出しが1ファイルに混ざります。Reactの .tsx と同じ設定を当てると、パーサーが .astro を読めずにこけます。
Astroのコンテンツサイトなら、Astro推奨設定を先に広げて、.astro 専用のルールを足します。
// eslint.config.js (Astro版)
import js from "@eslint/js";
import { defineConfig, globalIgnores } from "eslint/config";
import astro from "eslint-plugin-astro";
import globals from "globals";
import tseslint from "typescript-eslint";
import prettier from "eslint-config-prettier";
export default defineConfig([
globalIgnores(["dist/", ".astro/", "node_modules/", "public/"]),
js.configs.recommended,
astro.configs["flat/recommended"],
astro.configs["jsx-a11y-recommended"],
tseslint.configs.recommendedTypeChecked,
{
files: ["**/*.{ts,tsx}"],
languageOptions: {
globals: { ...globals.browser, ...globals.node },
parserOptions: {
projectService: true,
tsconfigRootDir: import.meta.dirname,
},
},
},
{
files: ["**/*.astro"],
rules: {
"astro/no-set-html-directive": "error", // 安易なHTML注入を止める
"astro/no-unused-define-vars-in-style": "error",
},
},
prettier, // ここでも最後
]);
ブログのようにMDXやAstroを多く扱うサイトでは、ESLintだけで整形まで完璧にしようとしない方が安定します。整形はPrettierへ、ESLintは危険なコード・アクセシビリティ・型の事故に寄せる。住み分けが効きます。
現場ごとにルールの強さを変える
同じ設定を全プロジェクトに使い回すと、どこかで無理が出ます。僕が実際に分けている3パターンです。
1. SaaS管理画面。ユーザー招待、請求、権限変更のような処理は、Promiseの握りつぶしが本番事故に直結します。no-floating-promises を error にして、クリック時の非同期処理は void handleSubmit() と意図を明示。Storybookのモックだけはanyを緩めます。
2. Astroの技術ブログ。記事本文・OGP・CTAコンポーネントが混ざるので、まず .astro のパース失敗を解消。astro/no-set-html-directive を強めると、安易なHTML注入をレビューで見落としにくくなります。生成済みコンテンツは ignores へ。
3. ライブラリ開発。公開APIでは型importの揺れ、未使用export、テストだけで使うanyが残りがち。本体は strictTypeChecked で厳しく、テストだけ緩める分離が効きます。設定全体を緩めると、利用者に届く型定義まで荒れます。
| 現場 | 強めるルール | 緩める場所 |
|---|---|---|
| React管理画面 | Promise、Hooks、a11y | Storybookのmock |
| Astroブログ | .astro、HTML注入、未使用CSS | 生成済みコンテンツ |
| TypeScriptライブラリ | 型import、未使用変数、export | test fixture |
この分け方は、コミット時の自動実行とも相性がいいです。保存やコミットのたびに変更ファイルだけlintしたいなら、Husky + lint-staged設定ガイドも合わせてどうぞ。
CIで npm run lint を固定する
ローカルで直っても、CIで同じコマンドが走らなければチームの品質はそろいません。「僕のマシンでは通る」は何の保証にもなりません。GitHub Actionsなら次の最小構成で十分です。
name: code-quality
on:
pull_request:
push:
branches: [main]
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 22
cache: npm
- run: npm ci
- run: npm run lint
- run: npm run typecheck
既存コードに違反が多い場合、いきなり --fix で全体を書き換えないでください。まず違反の種類を集計してから、危険度の高いルールだけ小さく直します。
npm run lint # まず全体の違反を見る
npm run lint -- --format json # 種類別に集計したいとき
npm run lint:fix # 自動修正できるものだけ直す
npm run lint # 残りを確認
npm run typecheck # 型も通す
npm run lint:debug # 設定が想定どおり当たっているか確認
最後の lint:debug で出した .eslint-debug.json を見れば、React用ルールが .astro に漏れていないか、テストだけ緩めたルールが本体に当たっていないかが分かります。
僕がやらかした失敗3つ
正直に書きます。Flat Config移行のとき、僕は同じ穴に何度も落ちました。
ひとつ目は、prettier を配列の真ん中に置いたこと。「整形オフだから先に書いとこ」と上の方に入れたら、その後ろのルールで整形系が復活して、保存のたびにコードが揺れました。eslint-config-prettier は必ず最後。これは鉄則です。
ふたつ目は、型ありlintを全ファイルに当てたこと。projectService: true は便利ですが、生成物・Storybook出力・ビルド済みJSまで読むとlintが2分かかります。globalIgnores を先に書く方が、後からCI時間を削るよりずっと楽でした。
みっつ目は、eslint --fix を一発で全部に走らせたこと。import順・未使用変数・型importが一気に変わって、PRが「挙動の差分」ではなく「整形の差分」に埋もれました。レビュアーに申し訳なかった。今はルール単位で小さくPRを分けます。
Claude Codeに設定を頼むときのコツ
「ESLint設定作って」だけだと、冒頭の僕みたいに重すぎる設定が返ってきます。渡すべきは「何を読んで、どのコマンドで、どこまで許すか」です。新規導入ならこのテンプレが手堅いです。
このリポジトリにESLint Flat Config(eslint.config.js)を導入してください。
対象はTypeScript + Reactです。まずpackage.json、tsconfig、既存のlint設定を読んでください。
方針: 整形はPrettierに任せ、ESLintは型の事故とアクセシビリティに寄せます。
recommendedTypeCheckedをベースに、生成物はglobalIgnoresで除外、配列の最後に
eslint-config-prettierを置いてください。
npm run lint と npm run typecheck が通るまで修正し、既存の挙動を変える自動修正は
避けてください。必要な場合は理由を説明してください。
「アクセシビリティは落としたくない」「テストfixtureのanyは許す」「公開APIは厳しく」のように、現場の事情を渡すほど設定が合います。AIは賢いですが、あなたのチームの優先順位は知りません。そこだけは人間が決める仕事です。
よくある質問
Q. .eslintrc から eslint.config.js に移行は必須ですか?
A. ESLint v9以降は Flat Config が標準で、.eslintrc 系は v10 で読み込み対象から外れました。新規なら最初から eslint.config.js、既存は早めの移行をおすすめします。
Q. 設定ファイルは .js と .mjs どっちがいい?
A. package.json に "type": "module" があるなら eslint.config.js、ないなら eslint.config.mjs が無難です。eslint.config.ts も使えますが、jiti(2.2.0以降)の追加インストールが要ります。まずは .js/.mjs で十分です。
Q. lintが遅いです。どこから削る?
A. まず globalIgnores で生成物・ビルド出力・カバレッジを除外します。それでも遅ければ、型情報が要らないファイル(設定ファイルなど)に disableTypeChecked を当てて型lintを外すと効きます。
Q. ESLintで整形(Prettier)まで全部やってはダメ?
A. やれなくはないですが、競合とメンテで疲れます。整形はPrettier、検査はESLintに分けた方が安定します。競合は eslint-config-prettier を配列の最後に置けば消えます。
Q. ルールはどこまで error にすべき?
A. CIで止めたいものだけ error、教育目的で眺めるだけなら warn、不要なら入れない。この3択がおすすめです。warn を増やしすぎると、半年後には誰も見なくなります。
実際に試した結果
この設定を、僕の検証用React管理画面とAstroブログ雛形に入れて試しました。初回は未処理Promiseとimport順の違反がどっと出ましたが、globalIgnores を先に書いておいたおかげで、lintが2分から十数秒に縮みました。prettier を末尾に固定してからは、保存のたびにコードが揺れる現象もぴたりと止まりました。
結局いちばん再現性が高かったのは、npm run lint と npm run typecheck をCIに固定し、Claude Codeには失敗ログを貼って小さく直させる運用です。賢い設定を一発生成させるより、壊れたらCIで止まる足場を先に組む。遠回りに見えて、これがいちばん速い、というのが今の実感です。
次の一歩としては、整形ルールの中身はPrettier設定カスタマイズガイド、コミット前の自動チェックはHusky + lint-staged設定ガイドへ。繰り返し使えるレビュー依頼文や導入チェックリストが欲しい方は、ClaudeCodeLabの教材ページにプロンプト集と導入テンプレートも置いています。公式の一次情報はESLintのConfiguration Filesとtypescript-eslintのTyped Lintingが基準です。
無料PDF: Claude Code はじめてのチートシート
まずは無料PDFで基本コマンドと最初の使い方をまとめて確認してください。登録後はそのままテンプレート集や導入相談にも進めます。
スパムは送りません。登録情報は厳重に管理します。
Claude Codeを仕事で使える形にしませんか?
まず無料PDFで基本を固め、繰り返し使う作業はGumroad教材へ、チーム導入や権限設計は導入相談へ進めます。
この記事を書いた人
Masa
Claude Codeの実務活用、導入設計、収益導線改善を検証しているエンジニア。10言語の技術メディアを運営中。
関連書籍・参考図書
この記事のテーマに関連する書籍を楽天ブックスで探せます。
※ 当サイトは楽天市場のアフィリエイトプログラムに参加しています。上記リンクから商品をご購入いただくと、運営者に紹介料が支払われる場合があります。
関連記事
Claude Codeに1ファイルだけ直させる指示文のつくり方
「もっと良くして」で40行も変えられた失敗から学んだ、触る範囲・検証・戻し方をセットにしたClaude Code用の依頼文テンプレートを紹介します。
Claude Code の権限拒否から復旧する: 止まった理由を次の安全手順に変える
Claude Code のコマンドが拒否されたとき、焦って許可を広げずに、拒否理由、代替手順、証拠コマンド、再試行条件へ分解する方法。
Claude Codeにビルド→スモークテスト→自動修正を回させる足場の作り方
最小スモークテストの選び方、失敗ログを食わせて直させるループ、回数上限と確認ゲートで暴走を止める方法を、コピペで動くコード付きで紹介します。