2025年8月19日
執筆
有限会社 WINGSプロジェクトが運営する、テクニカル執筆コミュニティ(代表 山田祥寛)に所属するテクニカルライター。新潟県上越市出身。会津大学コンピュータ理工学部卒業後、現在は新潟市に在住。ReactやAndroidを軸に、モバイルアプリ開発やWebサイト制作、Webメディア編集部の業務改善や、プログラミング技術記事の執筆等に携わっている。著書に「たった1日で基本が身に付く! Androidアプリ開発超入門」(技術評論社)、「基礎から学ぶ React Native入門」(翔泳社)他。
監修
静岡県榛原町生まれ。一橋大学経済学部卒業後、NECにてシステム企画業務に携わるが、2003年4月に念願かなってフリーライターに転身。Microsoft MVP for Visual Studio and Development Technologies。執筆コミュニティ「WINGSプロジェクト」代表。
主な著書に「独習」シリーズ、「これからはじめるReact実践入門」、「改訂3版 JavaScript本格入門」他、著書多数。
第2回ではコンポーネント分割について学びましたが、今回はもう1つの重要な設計決定である「フォルダ設計」に焦点を当てます。実は、React公式ドキュメントを見ても、フォルダ構造についての明確な規定は存在しません。他のフレームワークではコマンドラインツールとの組み合わせで所定のフォルダに対するコード生成を行うケースもありますが、Reactでは自由に決められます。
これは一見すると不親切に感じるかもしれませんが、実は柔軟性を示す重要な特徴でもあります。フォルダ設計はフレームワークの要求ではなく、開発チームの選択なのです。あなた方がつくるプロダクトの規模や特性に応じて、最も都合がいいフォルダ構造を選択して構いません。
まず本稿では、フォルダ設計の基礎理論として、なぜ機能別分類が重要なのかを解説します。
さて、いくつかの選択肢を挙げていくことになりますが、トップバッターとして是非紹介しておきたい構造が「フラットに並べる」ことです。つまり、すべてのコンポーネントを1つのフォルダの直下に配置して、フォルダ構造をつくらない、ということです。このアプローチは、一見すると設計をサボったようにも見えるかもしれません。ですが、小規模なプロジェクトでは最も効率的な選択肢です。
フラット構造の最大のメリットは「迷わないこと」です。コンポーネントを作成する際に「これはどのフォルダに入れるべきか?」という判断に時間を費やす必要がありません。
src/ formatPrice.ts Header.tsx Navigation.tsx ProductCard.tsx UserProfile.tsx LoginForm.tsx SearchBox.tsx useAuth.tsx useProduct.tsx validateEmail.ts
フラット構造を採用する際は、最低限の整理ルールを設けることが重要です。ファイル命名では、機能を表すプレフィックスを統一する(useAuth.tsx
、formatPrice.ts
など)、拡張子を適切に使い分ける(TypeScriptは .ts
、Reactコンポーネントは .tsx
)といった基本的な規則を徹底しましょう。また、ファイル先頭にコメントで目的を明記しておくと、後から見返す際に理解しやすくなります。
ファイル数が10〜20程度であれば、このような構造でも十分に管理可能です。IDEの検索機能を使えば目的のファイルもすぐに見つかりますし、何より「過度な設計」によるオーバーエンジニアリングを避けることができます。
しかし、プロジェクトが成長すると課題が見えてきます。ファイル数が20を超える頃から、次のような困りごとが発生し始めます。
1つの目安として、コンポーネント数が20〜30を超えたタイミングで、何らかのフォルダ設計を導入することを検討すべきでしょう。それ以下の小さなツールをつくる際に複雑なフォルダ構造を導入するのはオーバーエンジニアリングにあたるので、特にスピード重視でささっとつくりたい時にはフラット構造を採用してください。
フラット構造から脱却する際の最初のアプローチとして、技術別分類があります。これは、ファイルを技術的な種類に応じて分類する手法です。Reactコンポーネントはcomponents/
フォルダに、カスタムフックはhooks/
フォルダに、ユーティリティ関数はutils/
フォルダに配置するといったパターンです。
src/ components/ Header.tsx ProductCard.tsx hooks/ useAuth.tsx useProduct.tsx utils/ formatPrice.ts validateEmail.ts
一見すると整理されているように見え、「コンポーネントを探すときはcomponents/
を見ればいい」という明確性があります。多くのプロジェクトで採用されている定番パターンでもあり、新しいメンバーにとっても理解しやすい構造です。
しかし、プロジェクトが成長すると以下のような課題が見えてきます。
useProduct.tsx
とProductCard.tsx
は密接に関連しているにも関わらず、物理的に離れた場所に配置されるこれに対して機能別分類は、関連するファイルを物理的に近接配置することで、これらの問題を解決します。
src/ features/ product/ ProductCard.tsx useProduct.tsx productUtils.ts auth/ LoginForm.tsx useAuth.tsx authUtils.ts
features/
直下に機能名を並べてみました。この構造では、商品に関連するすべてのファイル(コンポーネント、フック、ユーティリティ)がproduct/
フォルダ内に配置され、認証に関連するファイルがauth/
フォルダ内にまとめられています。技術的な種類ではなく、ビジネス機能を軸とした分類になっているのが特徴です。
機能別分類の優位性は以下の3点に集約されます。
product/
フォルダ内のファイルに注力すればよいことが明確になるこれらの基本的なメリットに加えて、機能別分類は開発プロセス全体にも好影響をもたらします。例えば、プルリクエストのレビュー時に、変更されたファイルが特定の機能フォルダ内に集中していれば、レビュアーは「この変更は商品機能に関するものだ」と即座に理解でき、関連するビジネスロジックや仕様を思い出しやすくなります。また、新機能のテスト範囲も機能フォルダ単位で明確になるため、QAプロセスの効率化にも寄与します。
理論的なメリットを理解したところで、実際のプロジェクトでよく見られる失敗パターンから、なぜ機能別分類が重要なのかをより深く理解していきましょう。これらの失敗例は、多くの開発チームが一度は経験する「あるある」な問題です。
最も典型的な失敗が、曖昧な分類基準により「どこに置けばいいか分からない」ファイルが適当な場所に配置されてしまうパターンです。
src/ components/ common/ # 「共通」という曖昧な基準 Button.tsx # 本当に共通? ProductModal.tsx # 商品専用なのに共通扱い UserAvatar.tsx # ユーザー機能専用 ProductCard.tsx # なぜここに? Modal.tsx # 複数機能で使用、配置に悩む utils/ # 技術的分類だが機能横断 formatPrice.ts # 商品関連 validateEmail.ts # 認証関連 constants.ts # 全アプリ共通
「共通」「utils」といった分類は一見合理的に見えますが、実際には判断基準が曖昧で「なんでも置き場」になりがちです。結果として、本当に共通のコンポーネントと特定機能に特化したコンポーネントが混在し、関連するファイルが物理的に分散してしまいます。
フォルダ構造が不適切だと、コンポーネント名でその役割を表現しようとして、似たような名前のコンポーネントが乱立する問題が発生します。
src/ components/ Product.tsx # 商品の何? ProductCard.tsx # 商品カード ProductItem.tsx # 商品アイテム ProductDetail.tsx # 商品詳細 ProductInfo.tsx # 商品情報 Item.tsx # 何のアイテム? Card.tsx # 何のカード?
機能別の明確な分類がないため、開発者は限られたコンポーネント名の中でその用途を区別しようとします。結果として、Product
、ProductCard
、ProductItem
など微妙に異なる名前が増殖し、「どれが何の役割なのか」を覚えるための認知負荷が増大します。機能別分類であれば、features/product/ProductCard.tsx
のように、フォルダ名とファイル名の組み合わせで明確に役割を表現できます。
技術別分類では、機能間の依存関係が見えにくくなり、知らず知らずのうちに循環依存が発生してしまう問題があります。
src/ components/ UserProfile.tsx # hooks/useProduct.tsx を使用 ProductCard.tsx # hooks/useUser.tsx を使用 hooks/ useUser.tsx # utils/productHelpers.ts を使用 useProduct.tsx # utils/userHelpers.ts を使用プレフィック utils/ userHelpers.ts # utils/productHelpers.ts を使用 productHelpers.ts # utils/userHelpers.ts を使用
このような構造では、UserとProductという本来独立すべき機能が相互に依存し合う複雑な関係が生まれがちです。機能別分類であれば、features/user/
と features/product/
という物理的な境界により、不適切な依存関係を早期に発見できます。また、もし機能間の連携が必要な場合は、明示的にfeatures/shared/
や上位レイヤーで処理するという設計判断を促せます。
これらの失敗パターンに共通するのは、「明確な分類指針の欠如」です。開発者は日々「このファイルはどこに置くべきか?」「どんな名前にすべきか?」「この依存関係は適切か?」といった細かな判断を重ねています。適切な指針がないと、これらの判断が属人的になり、プロジェクト全体の一貫性が失われてしまいます。
機能別分類は、このような日常的な判断に明確な基準を提供します。特定機能に関連するファイルは迷わず features/[機能名]/
に配置し、真に共通のUIコンポーネントのみを components/ui/
に配置するという明確なルールがあれば、開発者は配置について悩む時間を削減できます。また、フォルダ名で機能を、ファイル名で具体的な役割を表現することで、命名の一貫性も保たれます。
さらに重要なのは、機能フォルダという物理的な境界が、不適切な依存関係の早期発見を促すことです。機能間の連携が必要な場合は、その依存関係を慎重に検討し、必要に応じて共通レイヤーで処理するという設計判断を自然に促せます。このように、機能別分類は単なる整理術ではなく、開発チーム全体の判断基準を統一する重要な仕組みなのです。
フォルダ設計において重要なのは、プロジェクトの成長段階に応じて適切なアプローチを選択することです。小規模ではフラット構造の効率性を活かし、成長に伴って技術別分類を経て、最終的に機能別分類へと移行する流れが自然です。
特に機能別分類は、関連ファイルの物理的近接、変更影響範囲の明確化、チーム分業の責任範囲明確化という3つの重要なメリットを提供します。これらは、現代のWebアプリケーション開発における複雑さと規模の増大に対する効果的な解決策となります。
しかし、理論を理解しただけでは実際のプロジェクトで活用することはできません。次回は、機能別分類を具体的にどう実装するか、Next.js App Routerとの組み合わせパターン、そしてプロジェクトの特性に応じた選択指針について詳しく解説します。実践的なフォルダ設計パターンを身につけて、より効率的な開発環境を構築していきましょう。
関連記事
人気記事