React Doctor でプロジェクトのコードの健康診断をしてみた

エンジニアの関口です。Reactプロジェクトのアンチパターンを検出してスコアリングしてくれるCLIツール「React Doctor」をCIに組み込んで運用してみました。指摘の内容は的確でしたが、9000行超の修正を一気にマージしたところデグレが発生しました。ツールの使い方だけでなく、実際のプロダクトでどう運用すべきかの教訓をまとめます。

https://www.react.doctor/

React Doctorとは

React Doctorは、Reactのコードベースをスキャンしてアンチパターンを検出し、0〜100のヘルススコアで採点してくれるOSSのCLIツールです。Million.jsの開発元が公開しています。

https://github.com/millionco/react-doctor

内部ではRust製のOxlintを使っており、大規模なプロジェクトでも高速に動きます。フレームワーク(Next.js、Vite、Remixなど)やReactのバージョンを自動検出し、プロジェクト構成に合わせた60以上のルールでチェックしてくれます。

チェックカテゴリは以下のとおりです。

  • State & Effects(不要なuseEffect、state管理のアンチパターン)
  • Performance(不必要な再レンダリング、メモ化の漏れ)
  • Architecture(prop drilling、コンポーネント設計)
  • Security / Correctness / Accessibility
  • Dead Code(未使用のファイル、エクスポート、型)

使い方

プロジェクトのルートで以下を実行するだけです。インストール不要で、npxでそのまま動きます。

npx -y react-doctor@latest .

--verboseを付けると、ルールごとに該当ファイルと行番号まで表示されます。

npx -y react-doctor@latest . --verbose

実行が速い

まずローカルで試して驚いたのは実行速度です。数百ファイルのプロジェクトに対して数秒で完了しました。

速さの理由は、内部で使われているOxlintにあります。OxlintはRust製のlinterです。JavaScriptベースのESLintと比べて桁違いに速く、公式ベンチマークでは50〜100倍速いとされています。

速いことの何がいいかというと、開発中にサッと回せる点です。「コミット前にちょっと確認」くらいの感覚で使えます。linterは遅いと回す頻度が下がり、指摘が溜まってから対処する羽目になります。React Doctorは速さのおかげで気軽に回せるので、問題を小さいうちに潰しやすいです。

この手軽さに気をよくして、CIにも組み込んでみることにしました。

CIに組み込んでみた

React DoctorはGitHub Actionsにも対応しており、GitHub Marketplace に公式Actionが公開されています。手順とYAMLの形は react-doctor の README(GitHub Actions) にまとまっています。

以下はその公式ドキュメントに沿った最小例です。

name: React Doctor

on:
  pull_request:

jobs:
  react-doctor:
    runs-on: ubuntu-latest
    permissions:
      contents: read
      pull-requests: write
    steps:
      - uses: actions/checkout@v5
        with:
          fetch-depth: 0 # required for --diff
      - uses: millionco/react-doctor@main
        with:
          diff: main
          github-token: ${{ secrets.GITHUB_TOKEN }}

ポイントをいくつか補足します。

fetch-depth: 0--diff でベースブランチとの差分だけをスキャンするために README 側で必須とされています。diff には比較元のブランチ名(例: main)を指定します。

github-tokenGITHUB_TOKEN を渡すと、pull_request イベント時に診断結果が PR コメントとして投稿されます。コメントを出さない場合はこの入力を省略します。

Action の入力には directoryverboseproject(モノレポのワークスペース)・node-version などがあります。ジョブの出力では score(0〜100)も参照できます。細かい指定は README の表を参照してください。

現時点では診断結果は情報提供として扱い、CI を失敗させるかどうかはチームで運用が安定してから判断する予定です。

ローカルでも同じスキャンを実行できるように、package.jsonにスクリプトを用意しています。

# 全ファイルスキャン
pnpm react-doctor

# mainブランチとの差分のみスキャン
pnpm react-doctor:diff

PRを出すと、React Doctorの診断結果がコメントとして自動投稿されます。

診断結果

自社のNext.js(App Router)アプリ @myapp/web に対してフルスキャンを実行しました。モノレポには NestJS の API などもありますが、React Doctor は React 向けの診断ツールなので、バックエンドの評価はここには載せていません。

パッケージ スコア エラー 警告 対象ファイル
@myapp/web (apps/) 83 / 100 13 267 116/187

@myapp/web が 83 / 100 で、指摘はここに集中していました。

エラー(13件)

エラーは即座に対応が必要なものです。

React Hooksの条件付き呼び出しが9件ありました。Hooksは全てのレンダリングにおいて同じ順序で呼ばれる必要があります。条件分岐の中でuseEffectuseStateを呼んでいる箇所が検出されています。

アクセシビリティ関連では、combobox roleにaria-controlsが不足している箇所が4件ありました。

主な警告(267件)

カテゴリ 件数 内容
useStateが多すぎる 27 5つ以上のuseStateを持つコンポーネント。useReducer推奨
useEffect内のsetStateが多い 19 1つのuseEffectで5つ以上のsetState
未使用ファイル 46 どこからもimportされていないファイル
未使用export 51 使われていないexport
未使用の型定義 53 使われていないtype
rechartsの遅延読み込み未対応 6 next/dynamicReact.lazy()推奨
Array indexをkeyに使用 6 安定した一意識別子を使うべき
next/image未使用 6 imgの代わりにnext/imageを使うべき
useEffectがイベントハンドラの代替 5 イベントハンドラに移すべき
dangerouslySetInnerHTML使用 1 XSS脆弱性リスク

未使用ファイル・export・型定義だけで150件あります。Dead Code検出がReact Doctorの指摘の大部分を占めていました。

診断結果からissueを作成

診断結果をもとに、カテゴリごとにissueを起票しました。

たとえば「大きいコンポーネントの分割検討」「useSearchParamsのSuspense boundary追加」などのissueを作り、それぞれ独立して対応できるようにしています。

ここまでは順調でした。問題はこの後です。

9000行の修正を一気にマージした結果

最初にやったのは、React Doctorの指摘をまとめてAIコーディングエージェントに修正を依頼することでした。エージェントは指摘を一通り読み取り、該当箇所をまとめて修正してPRを出してくれました。

結果として、9000行を超える差分のPRができあがりました。レビューして一気にマージしたところ、デグレが発生しました。

原因はいくつかありました。

  • 修正の影響範囲が広すぎて、レビューで見落としが起きた
  • 複数人が並行して作業しているブランチとの衝突が大きかった
  • ダミーデータ用に「今は使っていないが今後使う予定」のコードをDead Codeとして削除してしまった

指摘そのものは正しくても、一括で直してまとめてマージするのはリスクが大きいと痛感しました。

学んだ運用のポイント

運用途中から入れるとき

新規でゼロからなら、最初からルールに寄せて書けば十分です。すでにコードと運用が積み上がっているところへ後から入れると、指摘の量と対応コストは新規のときとは段違いです。

自社では、運用の途中から ESLint や Jest を入れた経験があります。ESLint は既存コードに警告やエラーが雪だるま式に増え、設定やルールとの折り合いに時間がかかる。Jest はテスト基盤を後付けするときの書き換えやモックまわりで、やはり一度に片づけきれない仕事が残る、といった具合でした。

React Doctor を後から足したときの手応えは、まさにそれと同じ種類のものでした。既存の資産に「あとからルールのレイヤー」が乗るので、指摘が一気に出ること、チームの Lint やコーディング規約と指摘の前提が食い違って見えること、どちらも体験として近いです。

だから運用中に導入するときは、「公式どおり全部を一度に厳格に」より、いまの書き方や運用から大きく外れない出し方が重要だと感じました。ignore でノイズを減らす、対象パッケージや diff だけに絞る、CI では失敗させずログだけ、など段階を踏むのが現実的です。

指摘ごとにissueを切る

まず運用の前提として、リポジトリ全体をいつもフルスキャンするのではなく、範囲を切ってチェックを回すことを基本にしたいです。--diff により PR の差分だけに絞る、--project により特定のパッケージだけにする、といった切り口を先に決めておくと、issue の粒度も小さく保ちやすくなります。リファクタの優先順位を付けたり、レビューにおいて何を見るかを揃えたりするときにも、同じ単位で物事を追いやすいです。

以前、AI に ESLint の警告を一気に全部直させたことがあります。洗い出しは速い一方で、抜け漏れが出たり、影響範囲を考えずに差分だけが膨らむ PR になったりしました。React Doctor においても同様です。全体の方針(どこまで直すか、どのルールから手を付けるか)をチームで決め、それを踏まえて部分的に適用していくのが望ましいと考えています。

issue はカテゴリやルール単位で切り、1つずつ対応するのが安全です。たとえば次のように分けると、PR の差分が小さくなりレビューしやすくなります。

  • react/no-array-index-key の修正
  • knip/exports の未使用エクスポート削除
  • react/no-unnecessary-use-effect の修正

1つの issue に対して 1つの PR を出す運用にしておけば、デグレが起きても原因の特定が楽です。

Dead Code検出は除外設定とセットで使う

React DoctorのDead Code検出は便利な機能です。未使用のエクスポートやファイルを見つけてくれます。ただし「今は使っていないけど将来使う予定のコード」や「ダミーデータ用に定義しているけどまだ未接続のコード」まで検出されます。

react-doctor.config.json で意図的に残しておきたいファイルを除外しておく必要があります。自社では以下のように設定しています。

{
  "ignore": {
    "rules": [],
    "files": [
      "**/*.test.tsx",
      "**/*.test.ts",
      "**/*.spec.tsx",
      "**/*.spec.ts",
      "**/__tests__/**",
      "**/__mocks__/**"
    ]
  },
  "verbose": false
}

テストファイルやモック関連は診断対象から外しています。テストコードはプロダクションコードとは書き方の基準が異なるため、ここにReact Doctorのルールを当てるとノイズが増えます。

ダミーデータ用のファイルや今後使う予定の未接続コードがある場合は、ignore.filesにそのパスを追加しておくとDead Code扱いを防げます。この設定を先に整えてからスキャンしないと、不要な削除によるデグレにつながります。

実際のプロダクトでは慎重にやるべき

ローカルで試す分にはリスクはほとんどありません。CI への組み込みや、エージェントへの修正依頼は、手順としては簡単です。一方で、出てきた修正をそのままマージするかどうかは別問題で、次のような場合は慎重に判断しました。

  • ユーザーに影響する画面を含む変更
  • 複数人が同時に作業しているブランチがあり、大きなリファクタリング PR が衝突の解消コストを増やしやすい時期
  • テストカバレッジが低く、デグレに気づきにくいコードベース

並行開発が激しいときは、修正の優先度を見直し、開発が落ち着いたタイミングを見計らってから当てる、という進め方も現実的でした。

エージェントのスキルとの兼ね合い

React Doctorにはコーディングエージェント向けのスキルインストール機能があります。

curl -fsSL https://react.doctor/install-skill.sh | bash

CursorやClaude Codeなど主要なエージェントに対応しています。インストールすると、エージェントがReact Doctorのベストプラクティスルールを参照しながらコードを書いてくれます。

ただし、既存のスキルやルールとの干渉には注意が必要です。自社ではエージェントのスキルをAIに管理させていましたが、React Doctorのスキルを追加したことで既存のスキルとの優先順位が曖昧になり、意図しない挙動が増えました。

ここから得た教訓は2つあります。

  • スキルの追加時は既存スキルとの整合性を確認する。新しいスキルが既存のルールと矛盾していないか、追加前にチェックする
  • AIにスキルを管理させても、定期的に人間が見直す。スキルが増えるにつれて相互作用が複雑になるため、棚卸しのタイミングを設ける

まとめ

React Doctorは、npxで即実行できる手軽さと的確な指摘が魅力のツールです。検出されるアンチパターンはどれも妥当で、コードベースの健康状態を把握するには十分な精度があります。

ただし「検出→一括修正→マージ」の流れをそのまま踏むと、デグレのリスクがあります。自社で実際にやってみて学んだポイントをまとめると以下のとおりです。

  • 指摘はカテゴリ単位でissueを切り、1つずつPRにする
  • 複数人で並行開発しているときは段階的に対応する
  • Dead Code検出は除外設定を先に整えてから使う
  • エージェントのスキルは追加後も定期的に見直す

検出ツールとしての完成度は高いので、「どう直すか」の運用設計をセットで考えるのが大事だと感じました。

弥生では一緒に働く仲間を募集しています。
www.yayoi-kk.co.jp
弥生のエンジニアに関する note 記事もご覧ください。
note.yayoi-kk.co.jp