コロケーション
コードは種類ではなく、変更の単位で近くに置きます。一つの画面を修正するときに編集するファイルが、離れたディレクトリへ散らばらない構成を選びます。
選択基準
使用箇所が一つのコンポーネント、Hook、型、定数は、使う画面や機能のディレクトリへ置きます。プロジェクト直下のcomponents/やhooks/へ、役割を問わず一括で分類しません。
共有する場所へ移すのは、二つ目の利用箇所が実際に現れてからです。将来の再利用を見込んだ先行配置はしません。tailwindのクラスが似ているだけの一致は共通化せず、業務上同じ意味を持つと確認できたものだけを昇格します。
app/invoices/
page.tsx # 画面の入口
invoice-table.tsx # この画面だけで使うコンポーネント
use-invoices.ts # この画面だけで使うHook
Next.jsのApp Routerでは、route以外のファイルはページとして公開されないため、routeディレクトリ内へ私有ファイルを置けます。TanStack Routerを使うプロジェクトでも、routeファイルの近くへ同じ基準で置きます。
共有する場所
プロジェクト直下のcomponents/は、shadcn/uiなど画面をまたぐ基盤UIに限定します。業務上の意味を持つコンポーネントは、複数の機能から使われるようになった時点で、機能間の共有ディレクトリへ移します。
昇格するとき、そのコンポーネントが元の画面の都合を引きずっていないかを確認します。特定画面のstateやデータ契約に依存したままの昇格は、共有ではなく癒着を広げます。
データ契約とテスト
データ契約は利用側の近くへ置きます。GraphQLのFragment、Hono RPCやRESTのendpointとqueryは、コンポーネントの「データとの境界」に従い、使うコンポーネントから追える場所に定義します。
テストは対象ファイルの隣へ.test.tsとして置きます。テストだけを別ツリーへ集約すると、実装の移動や削除でテストが取り残されます。
避ける構成
barrel file(index.tsでの再export)を各ディレクトリへ習慣で置きません。循環参照と不要なモジュール読み込みの原因になり、コロケーションで得た依存の追いやすさを打ち消します。importは実ファイルへ直接向けます。
種類別の深い分類(components/atoms/など)も採用しません。分類の粒度をめぐる判断が増えるだけで、変更の単位と一致しないためです。