「ESLint 9でFlat Configに変わって設定がわからない」「PrettierとESLintのルール衝突をどう避ける?」「React・TypeScript・Next.jsでベストな組み合わせは?」——本記事はESLint 9.x(Flat Config)+ Prettier 3.xを軸に、ゼロからのインストールからtypescript-eslint・React・Next.js・Husky・GitHub Actionsまで、コピペで動く実コード40本以上で解説する完全設定ガイドです。eslint-config-prettierでの責務分離、prettier-plugin-tailwindcssの順序統一、lint-stagedによるpre-commit、CI/CDでの差分lintまで、2026年時点での実戦標準を一気通貫で組み立てます。
※ 個別フレームワーク(Next.js / Remix / Vite等)の詳細は姉妹記事 frameworks一覧 を参照してください。本記事は「すべてのプロジェクトに必要な共通土台」としてのlinter/formatter設計に集中します。
- ESLint 9 + Prettier 3の全体像 — 2026年の設定事情
- ESLint 9 のインストールと最小構成 — まず1ファイルで動かす
- files / ignores / globalIgnores — パスマッチの設計
- typescript-eslint v8 — TypeScript対応のセットアップ
- React + React Hooks + JSX a11y — フロント標準セット
- eslint-plugin-import / unicorn / vitest — 品質強化プラグイン
- Prettier 3 のインストールと基本設定
- eslint-config-prettier — 衝突を完全に止める
- prettier-plugin-tailwindcss / プラグイン群
- Next.js プロジェクトでの設定
- レガシー .eslintrc.* からの移行ガイド
- VSCode連携 — formatOnSave / codeActionsOnSave
- Husky + lint-staged — pre-commit hook
- GitHub Actions — CIでの lint チェック
- tsconfig との連携 — 型情報を共有する
- 運用Tips — キャッシュ・ルール設計・トラブルシュート
- まとめ — 2026年のESLint + Prettier鉄板構成
ESLint 9 + Prettier 3の全体像 — 2026年の設定事情
2024年4月にリリースされたESLint v9でFlat Config(eslint.config.js)がデフォルトとなり、長らく標準だった.eslintrc.* + extends方式は終了しました。同時にPrettierもv3に上がり、ESM対応・末尾カンマのデフォルト変更・プラグインAPIの整理が行われました。「過去の.eslintrc.jsonから書き写したらどこにも当たらない」状態を避けるため、まずは思想を押さえます。
Flat Configの基本思想
- JS/MJS/TSファイル1枚で完結:
extendsは廃止。配列を...spreadで合成 - 明示的な
files/ignores: パスマッチが配列単位で明確 - plugin/parserはオブジェクト渡し: 文字列指定の魔術が消えた
- 環境(env)廃止 →
languageOptions.globals:globalsパッケージで明示
ESLintとPrettierの責務分離 — 鉄則
「ESLintで整形もする」のはアンチパターンです。役割を以下のように完全分離します。
| 役割 | 担当 | 対象 |
|---|---|---|
| コード品質(バグ検出) | ESLint | 未使用変数・型の誤用・hooksルール・a11y等 |
| 整形(見た目) | Prettier | インデント・改行・カンマ・引用符 |
| 衝突回避 | eslint-config-prettier | ESLintの整形系ルールを無効化 |
必要バージョン早見表(2026年5月時点)
| ツール | 推奨バージョン | 備考 |
|---|---|---|
| Node.js | 20.x LTS or 22.x | 22.x推奨 |
| ESLint | 9.15.x 以上 | Flat Configデフォルト |
| typescript-eslint | 8.15.x 以上 | v8でFlat完全対応 |
| Prettier | 3.3.x 以上 | 末尾カンマall既定 |
| TypeScript | 5.5 以上 | moduleResolution: “bundler” |
ESLint 9 のインストールと最小構成 — まず1ファイルで動かす
いきなり巨大な設定を書く前に、最小構成で「動く」状態を作ります。これがすべての出発点です。
ESLintのインストール
# pnpm利用前提(npm/yarnでもOK)
pnpm add -D eslint@^9 @eslint/js globals
# 動作確認
pnpm exec eslint --version
# v9.15.0
最小のeslint.config.js(CommonJSなし、ESM固定)
// eslint.config.js
import js from "@eslint/js";
import globals from "globals";
/** @type {import('eslint').Linter.Config[]} */
export default [
js.configs.recommended,
{
files: ["**/*.{js,mjs,cjs}"],
languageOptions: {
ecmaVersion: 2024,
sourceType: "module",
globals: {
...globals.node,
...globals.browser,
},
},
rules: {
"no-unused-vars": "warn",
"no-console": ["warn", { allow: ["warn", "error"] }],
},
},
];
package.json側のtype設定
{
"name": "my-app",
"type": "module",
"scripts": {
"lint": "eslint .",
"lint:fix": "eslint . --fix"
}
}
初回実行とエラーの読み方
pnpm lint
# /src/index.js
# 3:7 warning 'unused' is assigned a value but never used
#
# /src/api.js
# 5:1 warning Unexpected console statement
# 自動修正可能なものだけ直す
pnpm lint:fix
commonjsで書きたい場合の例(レガシー対応)
// eslint.config.cjs(package.json に "type": "module" を入れない場合)
const js = require("@eslint/js");
const globals = require("globals");
module.exports = [
js.configs.recommended,
{
languageOptions: {
globals: { ...globals.node },
},
},
];
files / ignores / globalIgnores — パスマッチの設計
Flat Configの肝は「どのファイルにどのルールを当てるか」を配列で明示することです。.eslintignoreは廃止され、ignoresキーで宣言します。
globalIgnores相当 — プロジェクト全体で除外
// eslint.config.js
import js from "@eslint/js";
export default [
// プロジェクト全体で常に除外したいパス
{
ignores: [
"node_modules/**",
"dist/**",
"build/**",
".next/**",
"coverage/**",
"**/*.min.js",
"public/**",
"**/generated/**",
],
},
js.configs.recommended,
];
filesでファイルタイプ別にルールを分ける
export default [
// TypeScriptソース
{
files: ["src/**/*.{ts,tsx}"],
rules: {
"no-console": "error",
},
},
// テストファイルだけはconsole許可
{
files: ["**/*.{test,spec}.{ts,tsx}", "tests/**/*"],
rules: {
"no-console": "off",
"no-magic-numbers": "off",
},
},
// configファイルはNode.jsグローバル
{
files: ["*.config.{js,ts,mjs}", "scripts/**/*"],
languageOptions: {
globals: { ...require("globals").node },
},
},
];
filesとignoresは配列単位で適用される
// 「TypeScriptファイルにだけrecommendedルールを当てる」例
import tseslint from "typescript-eslint";
export default [
...tseslint.configs.recommended.map((config) => ({
...config,
files: ["**/*.{ts,tsx}"],
})),
];
typescript-eslint v8 — TypeScript対応のセットアップ
2024年に出たtypescript-eslint v8でFlat Configに完全対応し、tseslint.config()ヘルパーで型安全に書けるようになりました。
typescript-eslintのインストール
pnpm add -D typescript typescript-eslint
# 単独パッケージで入れる場合(細かい制御が必要なとき)
pnpm add -D @typescript-eslint/parser @typescript-eslint/eslint-plugin
tseslint.config()でTS基本セットを組む
// eslint.config.js
import js from "@eslint/js";
import tseslint from "typescript-eslint";
import globals from "globals";
export default tseslint.config(
{ ignores: ["dist/**", ".next/**", "node_modules/**"] },
js.configs.recommended,
...tseslint.configs.strict,
...tseslint.configs.stylistic,
{
files: ["**/*.{ts,tsx}"],
languageOptions: {
ecmaVersion: 2024,
sourceType: "module",
globals: { ...globals.browser, ...globals.node },
parserOptions: {
projectService: true,
tsconfigRootDir: import.meta.dirname,
},
},
rules: {
"@typescript-eslint/consistent-type-imports": "error",
"@typescript-eslint/no-unused-vars": [
"error",
{ argsIgnorePattern: "^_", varsIgnorePattern: "^_" },
],
"@typescript-eslint/no-explicit-any": "warn",
},
},
);
projectService(v8の新型解決)を有効化
// 旧:project: true (重い・遅い)
// 新:projectService: true(必要なtsconfigを動的探索・高速)
{
languageOptions: {
parserOptions: {
projectService: true,
tsconfigRootDir: import.meta.dirname,
},
},
}
strict / stylisticの違い
// strict = バグになりうるパターンを厳しめに検出
// stylistic = 一貫性のあるTypeScript的スタイル
// 推奨:両方入れる(整形系はPrettier担当だが「型の書き方」は別物)
export default tseslint.config(
...tseslint.configs.strict,
...tseslint.configs.stylistic,
);
型を必要とするルール群(type-checked)
// より厳密な型情報ベースのルールを使いたいときは
export default tseslint.config(
...tseslint.configs.strictTypeChecked,
...tseslint.configs.stylisticTypeChecked,
{
files: ["**/*.{ts,tsx}"],
languageOptions: {
parserOptions: { projectService: true, tsconfigRootDir: import.meta.dirname },
},
},
);
React + React Hooks + JSX a11y — フロント標準セット
ReactプロジェクトではESLintで「Hooksルール違反」「key忘れ」「a11y欠落」を防ぐのが定石です。3プラグインで構成します。
Reactプラグイン群のインストール
pnpm add -D
eslint-plugin-react
eslint-plugin-react-hooks
eslint-plugin-jsx-a11y
globals
React向けのeslint.config.js
import js from "@eslint/js";
import tseslint from "typescript-eslint";
import react from "eslint-plugin-react";
import reactHooks from "eslint-plugin-react-hooks";
import jsxA11y from "eslint-plugin-jsx-a11y";
import globals from "globals";
export default tseslint.config(
{ ignores: ["dist/**", "build/**"] },
js.configs.recommended,
...tseslint.configs.recommended,
{
files: ["**/*.{jsx,tsx}"],
plugins: {
react,
"react-hooks": reactHooks,
"jsx-a11y": jsxA11y,
},
languageOptions: {
parserOptions: {
ecmaFeatures: { jsx: true },
},
globals: { ...globals.browser },
},
settings: {
react: { version: "detect" },
},
rules: {
...react.configs.recommended.rules,
...react.configs["jsx-runtime"].rules,
...reactHooks.configs.recommended.rules,
...jsxA11y.configs.recommended.rules,
// React 17+ では import React 不要
"react/react-in-jsx-scope": "off",
"react/prop-types": "off",
},
},
);
react-hooksルールの効果
// ❌ rules-of-hooksに違反 — 条件分岐内でhooks
function Bad({ flag }: { flag: boolean }) {
if (flag) {
const [x, setX] = useState(0); // eslint: React Hook called conditionally
}
return null;
}
// ❌ exhaustive-deps — depsに変数漏れ
function Bad2({ id }: { id: string }) {
useEffect(() => {
fetch(`/api/${id}`);
}, []); // eslint: React Hook useEffect has a missing dependency 'id'
}
jsx-a11yで防げる典型例
// ❌ alt属性が欠落
<img src="/logo.png" />
// ❌ クリックハンドラだけでキーボード操作不可
<div onClick={handleClick}>Click</div>
// ⭕ button要素を使う or role/tabIndex/onKeyDownを追加
<button type="button" onClick={handleClick}>Click</button>
eslint-plugin-import / unicorn / vitest — 品質強化プラグイン
標準ルールだけでは検出しきれない「importの順序」「危ない記法」「テスト記述漏れ」をプラグインで補強します。
importプラグイン
pnpm add -D eslint-plugin-import eslint-import-resolver-typescript
import順序を強制する
import importPlugin from "eslint-plugin-import";
export default [
{
plugins: { import: importPlugin },
settings: {
"import/resolver": {
typescript: { project: "./tsconfig.json" },
node: true,
},
},
rules: {
"import/order": [
"error",
{
groups: [
"builtin",
"external",
"internal",
["parent", "sibling", "index"],
"type",
],
"newlines-between": "always",
alphabetize: { order: "asc", caseInsensitive: true },
},
],
"import/no-duplicates": "error",
"import/no-cycle": ["error", { maxDepth: 3 }],
},
},
];
unicornプラグイン — モダンJSのベストプラクティス
pnpm add -D eslint-plugin-unicorn
import unicorn from "eslint-plugin-unicorn";
export default [
unicorn.configs["flat/recommended"],
{
rules: {
// ファイル名のkebab-case強制が厳しすぎる場合
"unicorn/filename-case": [
"error",
{ cases: { kebabCase: true, pascalCase: true } },
],
// 過度に制限的なルールはoffにする
"unicorn/prevent-abbreviations": "off",
"unicorn/no-null": "off",
},
},
];
vitestプラグイン — テストコード専用
pnpm add -D eslint-plugin-vitest @vitest/eslint-plugin
import vitest from "@vitest/eslint-plugin";
export default [
{
files: ["**/*.{test,spec}.{ts,tsx}"],
plugins: { vitest },
rules: {
...vitest.configs.recommended.rules,
"vitest/expect-expect": "error",
"vitest/no-focused-tests": "error",
"vitest/no-disabled-tests": "warn",
},
},
];
Prettier 3 のインストールと基本設定
整形はPrettierに完全委譲します。ESLintでindentやquotesを設定するのは禁止です。
Prettierのインストール
pnpm add -D prettier@^3
# バージョン確認
pnpm exec prettier --version
# 3.3.3
.prettierrc — プロジェクト共通の整形ルール
{
"printWidth": 100,
"tabWidth": 2,
"useTabs": false,
"semi": false,
"singleQuote": true,
"quoteProps": "as-needed",
"jsxSingleQuote": false,
"trailingComma": "all",
"bracketSpacing": true,
"bracketSameLine": false,
"arrowParens": "always",
"endOfLine": "lf"
}
.prettierignore
# 自動生成・依存・ビルド成果物
node_modules
dist
build
.next
coverage
public
*.min.js
*.min.css
pnpm-lock.yaml
package-lock.json
yarn.lock
Prettierのコマンド実行
# すべて整形チェック
pnpm exec prettier --check .
# 整形して書き戻す
pnpm exec prettier --write .
# 差分があるファイルだけ
pnpm exec prettier --write "src/**/*.{ts,tsx,js,jsx,json,md,css}"
package.jsonにスクリプト追加
{
"scripts": {
"format": "prettier --write .",
"format:check": "prettier --check .",
"lint": "eslint .",
"lint:fix": "eslint . --fix",
"check": "pnpm format:check && pnpm lint"
}
}
eslint-config-prettier — 衝突を完全に止める
ESLintの整形系ルール(quotes, indent等)とPrettierが衝突すると、save時に修正が往復します。eslint-config-prettierでESLint側の整形系を全部offにします。
インストールと設定
pnpm add -D eslint-config-prettier
// eslint.config.js — 配列の最後に必ず置く
import js from "@eslint/js";
import tseslint from "typescript-eslint";
import prettierConfig from "eslint-config-prettier";
export default tseslint.config(
js.configs.recommended,
...tseslint.configs.recommended,
// ↓ 必ず最後。整形系ルールを全部offにする
prettierConfig,
);
eslint-plugin-prettierは使わない(推奨2026年)
// ❌ NG:eslint-plugin-prettier で Prettier を ESLint 経由で走らせる
// → 遅い・エラー出力が読みにくい・editor連携が二重起動になる
// 公式も非推奨。Prettierは独立CLI / IDE拡張で走らせる
// ⭕ 正解:eslint-config-prettierでルール衝突だけ止める
衝突しているか確認するヘルパー
# config-prettierにはcheck CLIがある
pnpm exec eslint-config-prettier src/index.ts
# No rules that are unnecessary or conflict with Prettier were found.
prettier-plugin-tailwindcss / プラグイン群
Prettier 3のプラグインAPIはシンプルになり、.prettierrcにpluginsキーで列挙するだけです。
Tailwindクラスの自動並び替え
pnpm add -D prettier-plugin-tailwindcss
{
"plugins": ["prettier-plugin-tailwindcss"],
"tailwindFunctions": ["clsx", "cn", "cva", "tw"],
"tailwindConfig": "./tailwind.config.ts"
}
並び替え前後の例
// before
<div className="text-white p-4 flex bg-blue-500 items-center justify-center" />
// after(プラグインが自動でcanonical順に並び替え)
<div className="flex items-center justify-center bg-blue-500 p-4 text-white" />
importを並び替えるプラグイン
pnpm add -D @ianvs/prettier-plugin-sort-imports
{
"plugins": [
"@ianvs/prettier-plugin-sort-imports",
"prettier-plugin-tailwindcss"
],
"importOrder": [
"<BUILTIN_MODULES>",
"^react$",
"^next(/.*)?$",
"<THIRD_PARTY_MODULES>",
"",
"^@/(.*)$",
"^[./]"
],
"importOrderTypeScriptVersion": "5.5.0"
}
プラグインの読込順注意
{
"plugins": [
"@ianvs/prettier-plugin-sort-imports",
"prettier-plugin-tailwindcss"
]
}
// tailwindプラグインは必ず配列の最後。
// 公式ドキュメントの指定:他のformatプラグインと協調するためtailwindは末尾固定。
Next.js プロジェクトでの設定
Next.js 15はeslint-config-nextがFlat Config対応(next/core-web-vitals)。create-next-appでもFlat Configが既定になりました。
Next.js向けのインストール
pnpm add -D
eslint@^9
@eslint/js
typescript-eslint
eslint-config-next
eslint-config-prettier
globals
eslint.config.js(Next.js 15)
import js from "@eslint/js";
import tseslint from "typescript-eslint";
import nextPlugin from "@next/eslint-plugin-next";
import reactHooks from "eslint-plugin-react-hooks";
import prettierConfig from "eslint-config-prettier";
export default tseslint.config(
{ ignores: [".next/**", "node_modules/**", "out/**"] },
js.configs.recommended,
...tseslint.configs.recommended,
{
files: ["**/*.{ts,tsx}"],
plugins: {
"@next/next": nextPlugin,
"react-hooks": reactHooks,
},
rules: {
...nextPlugin.configs.recommended.rules,
...nextPlugin.configs["core-web-vitals"].rules,
...reactHooks.configs.recommended.rules,
},
},
prettierConfig,
);
next lintは2026年に廃止予定
# Next.js 15.3+ では next lint がdeprecatedに
# package.jsonを差し替え
{
"scripts": {
"lint": "eslint .",
"lint:fix": "eslint . --fix"
}
}
App Router固有のルール
{
files: ["app/**/*.{ts,tsx}"],
rules: {
// server componentでuseStateを使ってないかチェック
"@next/next/no-html-link-for-pages": "off",
// imageは<Image>を使う
"@next/next/no-img-element": "error",
},
}
レガシー .eslintrc.* からの移行ガイド
「v8系の.eslintrc.jsonがある」「extendsで継承していた」プロジェクトを段階的にFlat Configへ移します。
公式の移行コマンド
# @eslint/migrate-config が変換を試みる
pnpm dlx @eslint/migrate-config .eslintrc.json
# → eslint.config.mjs が生成される
extendsの典型置換パターン
// before(eslintrc)
{
"extends": [
"eslint:recommended",
"plugin:@typescript-eslint/recommended",
"plugin:react/recommended",
"prettier"
]
}
// after(flat config)
import js from "@eslint/js";
import tseslint from "typescript-eslint";
import react from "eslint-plugin-react";
import prettierConfig from "eslint-config-prettier";
export default [
js.configs.recommended,
...tseslint.configs.recommended,
react.configs.flat.recommended,
prettierConfig,
];
env置換
// before
{ "env": { "browser": true, "node": true, "es2024": true } }
// after
import globals from "globals";
{
languageOptions: {
ecmaVersion: 2024,
globals: { ...globals.browser, ...globals.node },
},
}
parserOptions置換
// before
{
"parser": "@typescript-eslint/parser",
"parserOptions": {
"project": "./tsconfig.json",
"ecmaVersion": 2022,
"sourceType": "module"
}
}
// after
{
languageOptions: {
parser: tseslint.parser,
parserOptions: {
projectService: true,
tsconfigRootDir: import.meta.dirname,
ecmaVersion: 2024,
sourceType: "module",
},
},
}
段階的移行のテクニック
# ESLINT_USE_FLAT_CONFIG=falseで一時的にv8互換動作
ESLINT_USE_FLAT_CONFIG=false pnpm eslint .
# 逆に新旧両立できる @eslint/eslintrc を使う(過渡期のみ)
pnpm add -D @eslint/eslintrc
// 旧.eslintrcの設定を一時的に取り込むブリッジ
import { FlatCompat } from "@eslint/eslintrc";
const compat = new FlatCompat({ baseDirectory: import.meta.dirname });
export default [
...compat.extends("airbnb-typescript"),
// ↑ Flat Config化されていないプリセットを一時的に持ち込める
];
VSCode連携 — formatOnSave / codeActionsOnSave
エディタ統合は「保存時に自動で整形・自動修正」の体験が肝です。
.vscode/settings.json — チーム共有設定
{
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.formatOnSave": true,
"editor.codeActionsOnSave": {
"source.fixAll.eslint": "explicit",
"source.organizeImports": "never"
},
"eslint.useFlatConfig": true,
"eslint.experimental.useFlatConfig": true,
"eslint.workingDirectories": [{ "mode": "auto" }],
"eslint.validate": [
"javascript",
"javascriptreact",
"typescript",
"typescriptreact"
],
"prettier.requireConfig": true,
"[typescript]": { "editor.defaultFormatter": "esbenp.prettier-vscode" },
"[typescriptreact]": { "editor.defaultFormatter": "esbenp.prettier-vscode" },
"[json]": { "editor.defaultFormatter": "esbenp.prettier-vscode" }
}
.vscode/extensions.json — 推奨拡張
{
"recommendations": [
"dbaeumer.vscode-eslint",
"esbenp.prettier-vscode",
"bradlc.vscode-tailwindcss",
"yoavbls.pretty-ts-errors"
]
}
「explicit」と「always」の違い
// "explicit" → Ctrl+S(明示的な保存)でだけ実行
// "always" → autoSaveでも毎回実行(うるさい)
// 推奨は "explicit"
"source.fixAll.eslint": "explicit"
Husky + lint-staged — pre-commit hook
「ローカルでlintしてからpushしてほしい」を強制するのがpre-commit hookです。Husky 9 + lint-staged 15が定番。
Huskyのセットアップ
pnpm add -D husky lint-staged
# Husky 9はワンライナーで初期化
pnpm exec husky init
# → .husky/pre-commit が生成される
.husky/pre-commit
#!/usr/bin/env sh
pnpm exec lint-staged
package.jsonへのlint-staged設定
{
"lint-staged": {
"*.{js,jsx,ts,tsx}": [
"eslint --fix",
"prettier --write"
],
"*.{json,md,css,html,yml,yaml}": [
"prettier --write"
]
}
}
ステージ済みファイルだけに当てる仕組み
# commit時に変更のあるファイルだけ実行される
git add src/foo.ts
git commit -m "feat: add foo"
# ✔ Preparing lint-staged...
# ✔ Running tasks for staged files...
# ✔ eslint --fix
# ✔ prettier --write
巨大プロジェクトでの最適化
{
"lint-staged": {
"*.{ts,tsx}": [
"eslint --fix --cache --cache-location .eslintcache",
"prettier --write --cache"
]
}
}
// --cacheで2回目以降が高速化
commit時のtsc型チェック(任意)
{
"lint-staged": {
"*.{ts,tsx}": [
"eslint --fix",
"prettier --write",
"bash -c 'tsc --noEmit'"
]
}
}
GitHub Actions — CIでの lint チェック
pre-commitだけでは「すり抜け」が起こりえます。CI側でPR毎にチェックすることで品質を担保します。
.github/workflows/lint.yml
name: Lint
on:
pull_request:
branches: [main]
push:
branches: [main]
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
with:
version: 9
- uses: actions/setup-node@v4
with:
node-version: 22
cache: pnpm
- name: Install
run: pnpm install --frozen-lockfile
- name: Lint
run: pnpm lint
- name: Format check
run: pnpm format:check
- name: Type check
run: pnpm exec tsc --noEmit
差分ファイルだけlintする(高速化)
- name: Get changed files
id: changed
uses: tj-actions/changed-files@v45
with:
files: |
**/*.ts
**/*.tsx
- name: Lint changed files
if: steps.changed.outputs.any_changed == 'true'
run: pnpm exec eslint ${{ steps.changed.outputs.all_changed_files }}
キャッシュの活用
- name: Cache ESLint
uses: actions/cache@v4
with:
path: .eslintcache
key: eslint-${{ runner.os }}-${{ hashFiles('**/eslint.config.js') }}
PRコメントに結果を投稿
- name: ESLint with annotations
uses: reviewdog/action-eslint@v1
with:
reporter: github-pr-review
eslint_flags: "src/"
tsconfig との連携 — 型情報を共有する
typescript-eslintはtsconfig.jsonを読みます。ESLint側との認識を合わせないと「リント通るのにビルドで落ちる」現象が起きます。
tsconfig.eslint.json — リント専用
{
"extends": "./tsconfig.json",
"include": [
"src/**/*",
"tests/**/*",
"*.config.ts",
"*.config.js",
"eslint.config.js"
],
"exclude": ["node_modules", "dist"]
}
eslint.config.js側で参照
{
languageOptions: {
parserOptions: {
project: "./tsconfig.eslint.json",
tsconfigRootDir: import.meta.dirname,
},
},
}
projectServiceで自動探索(推奨)
// 手動でtsconfigを指定する必要なし
{
languageOptions: {
parserOptions: {
projectService: true,
tsconfigRootDir: import.meta.dirname,
},
},
}
// プロジェクトサービスがファイルに最も近いtsconfigを自動選択
monorepo対応
// pnpm workspace の各パッケージにtsconfigがある場合
{
languageOptions: {
parserOptions: {
projectService: {
allowDefaultProject: ["*.js", "*.mjs"],
defaultProject: "./tsconfig.json",
},
tsconfigRootDir: import.meta.dirname,
},
},
}
運用Tips — キャッシュ・ルール設計・トラブルシュート
動かしたあとは「速く・壊れず・チームで揃う」運用が大事です。
ESLint キャッシュを有効化
{
"scripts": {
"lint": "eslint . --cache --cache-location node_modules/.cache/eslint/"
}
}
ルールはerror / warn / offの3階層で運用
// "error" → CIで失敗。コミット禁止
// "warn" → 警告のみ。徐々に直したい新ルール
// "off" → 完全無効化
{
rules: {
"@typescript-eslint/no-explicit-any": "warn", // 既存コードがあるので段階導入
"@typescript-eslint/no-unused-vars": "error", // 必ず守る
"react/prop-types": "off", // TypeScript環境では不要
},
}
「特定行だけ無効化」コメント
// 1行だけ無効化
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const data: any = await res.json()
// 範囲で無効化
/* eslint-disable no-console */
console.log("debug 1")
console.log("debug 2")
/* eslint-enable no-console */
–debug でルール解決をトレース
pnpm exec eslint src/index.ts --debug 2>&1 | head -50
# どのconfigがどの順で当たったか全て確認できる
–print-config で実効設定を確認
pnpm exec eslint --print-config src/index.ts > effective-config.json
# あるファイルに対する最終的なルール集合をJSON出力
typescript-eslintが遅いとき
# 1. projectService に切り替える(v8の新解決)
# 2. tsconfig.eslint.jsonで includes を絞る
# 3. --cache を必ず付ける
# 4. パッケージを最新へ
pnpm up -L typescript-eslint @typescript-eslint/parser @typescript-eslint/eslint-plugin
まとめ — 2026年のESLint + Prettier鉄板構成
本記事で組み上げた構成を最後に1ファイルに集約して掲載します。このままeslint.config.jsとして置けば、TypeScript + React + Next.js + Prettierがすべて噛み合います。
最終形:全部入りeslint.config.js
import js from "@eslint/js";
import tseslint from "typescript-eslint";
import react from "eslint-plugin-react";
import reactHooks from "eslint-plugin-react-hooks";
import jsxA11y from "eslint-plugin-jsx-a11y";
import importPlugin from "eslint-plugin-import";
import unicorn from "eslint-plugin-unicorn";
import nextPlugin from "@next/eslint-plugin-next";
import prettierConfig from "eslint-config-prettier";
import globals from "globals";
export default tseslint.config(
{
ignores: [
"node_modules/**",
"dist/**",
"build/**",
".next/**",
"out/**",
"coverage/**",
"**/*.min.js",
],
},
js.configs.recommended,
...tseslint.configs.strict,
...tseslint.configs.stylistic,
unicorn.configs["flat/recommended"],
{
files: ["**/*.{ts,tsx,js,jsx}"],
plugins: {
react,
"react-hooks": reactHooks,
"jsx-a11y": jsxA11y,
import: importPlugin,
"@next/next": nextPlugin,
},
languageOptions: {
ecmaVersion: 2024,
sourceType: "module",
parserOptions: {
projectService: true,
tsconfigRootDir: import.meta.dirname,
ecmaFeatures: { jsx: true },
},
globals: { ...globals.browser, ...globals.node },
},
settings: {
react: { version: "detect" },
"import/resolver": {
typescript: { project: "./tsconfig.json" },
node: true,
},
},
rules: {
...react.configs.recommended.rules,
...react.configs["jsx-runtime"].rules,
...reactHooks.configs.recommended.rules,
...jsxA11y.configs.recommended.rules,
...nextPlugin.configs["core-web-vitals"].rules,
"react/react-in-jsx-scope": "off",
"react/prop-types": "off",
"@typescript-eslint/consistent-type-imports": "error",
"@typescript-eslint/no-unused-vars": [
"error",
{ argsIgnorePattern: "^_" },
],
"import/order": [
"error",
{
groups: [
"builtin",
"external",
"internal",
["parent", "sibling", "index"],
"type",
],
"newlines-between": "always",
alphabetize: { order: "asc", caseInsensitive: true },
},
],
"unicorn/prevent-abbreviations": "off",
"unicorn/no-null": "off",
},
},
{
files: ["**/*.{test,spec}.{ts,tsx}"],
rules: {
"no-console": "off",
"@typescript-eslint/no-explicit-any": "off",
},
},
// 最後に必ずprettier。整形系のESLintルールを全部offに
prettierConfig,
);
最終形:.prettierrc
{
"printWidth": 100,
"tabWidth": 2,
"semi": false,
"singleQuote": true,
"trailingComma": "all",
"arrowParens": "always",
"endOfLine": "lf",
"plugins": [
"@ianvs/prettier-plugin-sort-imports",
"prettier-plugin-tailwindcss"
],
"importOrder": [
"<BUILTIN_MODULES>",
"^react$",
"^next(/.*)?$",
"<THIRD_PARTY_MODULES>",
"",
"^@/(.*)$",
"^[./]"
]
}
最終形:package.json scripts
{
"scripts": {
"lint": "eslint . --cache",
"lint:fix": "eslint . --cache --fix",
"format": "prettier --write .",
"format:check": "prettier --check .",
"typecheck": "tsc --noEmit",
"check": "pnpm format:check && pnpm lint && pnpm typecheck",
"prepare": "husky"
},
"lint-staged": {
"*.{ts,tsx,js,jsx}": ["eslint --fix --cache", "prettier --write"],
"*.{json,md,css,yml}": ["prettier --write"]
}
}
関連記事
個別フレームワークの設定例はframeworksカテゴリ一覧から、TypeScript周辺はTypeScriptカテゴリから辿れます。
「設定しても続けるのが難しい」「実プロジェクトで他人のコードと折り合いを付けたい」と感じたら、現場経験ベースで質問できる学習環境が役に立ちます。テックアカデミーのフロントエンドコース、侍エンジニアのマンツーマン指導、DMM WEBCAMP、即戦力人材としてはレバテックの求人検索などを比較すると、自分の段階に合った選び方ができます。

コメント