naoki.dev

記事検索

タイトル・本文・タグから検索します

ホームに戻る
フロントエンド

Next.js モノレポで ESLint / Prettier / husky を組み、コミット前に品質を固める

目次

フォーマットや Lint は「気をつける」より「コミット時に自動で直す・止める」方が確実です。この記事では、frontend/ に Next.js アプリを置いたモノレポ構成で、git ルートの husky から frontend の ESLint / Prettierlint-staged 経由で動かす実際の設定をまとめます。console.logany・未使用変数/import の禁止、import 整列、Tailwind クラス整列まで、コミット前に自動で効かせます。

前提スタックは Next.js 16(ESLint flat config)/ TypeScript / Tailwind CSS v4 / pnpm です。

構成の全体像

ポイントは git のルートとパッケージの場所がずれていることです。

sample-app/   ← git ルート(.git はここ)
├── package.json           ← husky + lint-staged だけを置く
├── .husky/pre-commit
├── lint-staged.config.mjs
└── frontend/              ← Next.js 本体(ESLint / Prettier はここ)
    ├── eslint.config.mjs
    └── .prettierrc.json

husky は git フックの仕組みなので git ルート基準で動きます。一方 ESLint / Prettier の設定はアプリのある frontend/ に置きたい。そこで「husky はルート、実際の Lint はルートから frontend/ に委譲する」形にします。

husky をルートに入れる

ルートに最小の package.json を用意し、husky と lint-staged を入れます。

# リポジトリのルートで
pnpm add -D husky lint-staged
pnpm exec husky init

husky init.husky/ が作られ、ルートの package.json"prepare": "husky" が追加されます。この prepare により pnpm install のたびにフックが自動セットアップされ、クローン直後の別メンバーの環境でも有効になります。

husky が実際にやっているのは、git の探索先をこう切り替えているだけです。

git config core.hooksPath .husky/_

.husky/pre-commit は自分で書き換えて、lint-staged を呼ぶだけにします。

# .husky/pre-commit
pnpm exec lint-staged

lint-staged からサブディレクトリの ESLint を呼ぶ

ここが一番のコツです。ESLint の flat config は カレントディレクトリを基準に設定を解決します。ルートから eslint を叩くと frontend/eslint.config.mjs を見つけられません。そこで pnpm --dir frontend exec で cwd を frontend/ に寄せて実行します。

// lint-staged.config.mjs(リポジトリのルート)
export default {
  // JS/TS: ESLint(品質チェック+自動修正)→ Prettier(整形)の順に実行。
  // --max-warnings=0: Next の ESLint 設定は多くを warning 扱いにするため、
  // warning もブロック対象にしてフックを実効的にする。
  "frontend/**/*.{js,jsx,ts,tsx,mjs,cjs}": [
    "pnpm --dir frontend exec eslint --fix --max-warnings=0",
    "pnpm --dir frontend exec prettier --write",
  ],
  // JS/TS 以外(整形のみ Prettier で実施)。
  "frontend/**/*.{json,css,scss,md,mdx}": "pnpm --dir frontend exec prettier --write",
};

lint-staged はマッチしたファイルの絶対パスをコマンドへ渡します。cwd が frontend/ でも、絶対パスは cwd 配下を指すので ESLint は設定を正しく解決できます。

--max-warnings=0 を付けているのは、Next の ESLint プリセットが多くのルールを warning にしており、ESLint は warning だけなら exit 0 になるためです。これを付けないと「警告は出るがコミットは通る」状態になります。

ESLint:禁止したいものを error にする

「見た目の統一」は Prettier、「禁止・エラーにしたいルール」は ESLint、と役割を分けます。flat config に独自ルールを足します。

// frontend/eslint.config.mjs
import { defineConfig, globalIgnores } from "eslint/config";
import nextVitals from "eslint-config-next/core-web-vitals";
import nextTs from "eslint-config-next/typescript";
import prettier from "eslint-config-prettier/flat";
import simpleImportSort from "eslint-plugin-simple-import-sort";
 
const eslintConfig = defineConfig([
  ...nextVitals,
  ...nextTs,
  // プロジェクト独自のコード品質ルール
  {
    plugins: { "simple-import-sort": simpleImportSort },
    rules: {
      // import / export の順序を自動整列(--fix で自動修正される)
      "simple-import-sort/imports": "error",
      "simple-import-sort/exports": "error",
      // console.log を禁止(デバッグ用途の console.warn / console.error は許可)
      "no-console": ["error", { allow: ["warn", "error"] }],
      // any 禁止
      "@typescript-eslint/no-explicit-any": "error",
      // 未使用の変数・import をエラーに(TS では未使用 import も未使用変数として検出される)。
      // _ で始まる引数だけは意図的な未使用として許可。
      "@typescript-eslint/no-unused-vars": [
        "error",
        { argsIgnorePattern: "^_", varsIgnorePattern: "^_" },
      ],
    },
  },
  // Prettier と衝突する「整形系」ルールを無効化し、整形は Prettier に一任する。
  // 他の設定より後(末尾付近)に置くこと。
  prettier,
  globalIgnores([".next/**", "out/**", "build/**", "next-env.d.ts"]),
]);
 
export default eslintConfig;

意図はこうです。

  • no-console: console.log は禁止。ただしデバッグに使う console.warn / console.error は許可。
  • @typescript-eslint/no-explicit-any: any を禁止し、型を書かせる。
  • @typescript-eslint/no-unused-vars: 未使用の変数・import をエラーに。TypeScript では未使用 import も「未使用の変数束縛」として検出されるので、これ 1 つで import と変数の両方を止められます。_ 始まりだけは意図的な未使用として許可。
  • simple-import-sort: import / export の順序をルール化。--fix で自動整列されるので、手で並べ替える必要がありません。

追加パッケージはこれだけです。

pnpm --dir frontend add -D eslint-config-prettier eslint-plugin-simple-import-sort

eslint-config-prettier は必ず最後

ESLint にも文字幅やクォートなど整形系のルールがあり、そのままだと Prettier と喧嘩します。eslint-config-prettier はそれらを無効化するだけの設定なので、配列の末尾(他ルールより後)に置いて上書きさせます。flat config では eslint-config-prettier/flat を import して 1 要素として並べます。

Prettier:整形ルール+Tailwind クラス整列

Prettier 側は整形ルールと、Tailwind クラスの並べ替えプラグインを設定します。

// frontend/.prettierrc.json
{
  "semi": true,
  "singleQuote": false,
  "trailingComma": "all",
  "printWidth": 100,
  "tabWidth": 2,
  "plugins": ["prettier-plugin-tailwindcss"],
  "tailwindStylesheet": "./app/globals.css"
}
pnpm --dir frontend add -D prettier prettier-plugin-tailwindcss

prettier-plugin-tailwindcssclassName の中身を公式推奨順に並べ替えます。Tailwind v4 は設定が CSS ベース@import "tailwindcss"; を書いた CSS がエントリ)になったため、tailwindConfig ではなく tailwindStylesheet でそのエントリ CSS を指定します。ここでは app/globals.css です。

実際にこう並び替わります。

// before
<div className="p-4 flex text-white bg-blue-500 items-center">
 
// after(レイアウト → ボックス → 余白 → 文字 の推奨順)
<div className="flex items-center bg-blue-500 p-4 text-white">

整形対象外は .prettierignore に書いておきます。

# frontend/.prettierignore
.next/
out/
build/
node_modules/
pnpm-lock.yaml
next-env.d.ts

コミット時の挙動

これで git commit するとステージ済みファイルにだけ処理が走り、次のように分かれます。

ケース 結果
整形の乱れ・import 順・Tailwind 並び 自動修正 → 自動で再ステージ → コミット成立
console.log / any / 未使用変数・import コミット中止(自分で直して再コミット)

--fix--write で直せるものは lint-staged が直して git add し直してくれるので、手作業は不要です。直せない違反(any など)はコミットが止まります。

lint-staged は処理前にステージしていない変更を一旦 stash で退避し、ステージした差分だけを対象に整形・Lint します。関係ない変更まで巻き込んで add してしまう心配はありません。

ハマりどころまとめ

  • flat config の解決はカレント基準。git ルートとパッケージがずれるなら pnpm --dir <pkg> exec eslint で cwd を寄せる。
  • eslint-config-prettier は末尾。前に置くと整形系ルールが復活して Prettier と衝突する。
  • --max-warnings=0。Next の ESLint 設定は多くが warning なので、これがないとコミットが素通りする。
  • Tailwind v4 は tailwindStylesheettailwindConfig ではなく CSS エントリを指す。
  • どうしてもフックを飛ばしたい時は git commit --no-verify。ただし常用しない。
  • tsc のようなファイル単位で動かない型チェックは lint-staged と相性が悪い。型チェックは pre-push か CI に置くのが無難。

まとめ

husky(ルート)→ lint-staged →(frontend/ に委譲した)ESLint / Prettier という 3 段で、コミット前に品質を固められます。禁止ルールは ESLint、整形は Prettier に分担させ、直せるものは自動修正・再ステージ、直せない違反はコミットを止める。フォーマット論争と Lint 漏れをコミット時点で片付けて、レビューを本質的な議論に集中させましょう。

関連記事