個人開発のフロントエンド周りの設計について
Published on March 29, 2025
Category: アーキテクチャ基礎
前書き
ここ1ヶ月本業と業務委託先で忙しく、ブログ更新できていませんでした。
インプットは行っていたものの、アウトプットもできていなかったので、ここからまた継続していけるように精進していきます。
現在、Beatbox関連の個人開発を行なっており、空いてる時間を見つけて、本プロジェクトのフロントエンド周りの設計について、改めて設計し直すことにしました。自身の思考をAIとの壁打ちを交えながら考えた設計を共有いたします。
まだドメイン分析などをしっかりと行えていないので、この構成を反映するのはまだ先になるかと思いますが、少しづつ進めていきたい.....!!!!
ディレクトリ構成の思想
まず大前提として、責務ごとにディレクトリを明確に分けています。この設計は「関心の分離」にあり、データ取得・状態管理・UI表示という異なる責務を明確に分けることで、コードの見通しが格段に向上します。
最終理想系は以下のような形を目指しています。前提方針として、UIコンポーネントをpackagesとして切り出し、データの受け取りと表示だけに徹するプレゼンテーショナルな設計とします。これにより、テスト容易性の向上、再利用性の確保、そして責務の明確な分離が実現しました。
【追記】
src/domainsにpagesを管理して、domainごとのディレクトリを管理しようと考えておりましたが、UIに対して、domain情報が入ってくるは責務が単一ではなくなっているため、削除しました。
修正前の構想が下記のPRに記述があります。
https://github.com/hirayamahiroto/beatSinkCentral-ui-proto/pull/33#issue-2957824280
packages/src/
├── design-system/ # デザインシステム基盤
│ ├── tokens/ # デザイントークン
│ ├── primitives/ # 基底コンポーネント (shadcnやjsutdのようなUIコンポーネントライブラリ)
│ └── components/ # 共通コンポーネント(atoms, molecules, organisms,templates)
│
│
├── shared/ # ユーティリティと共通機能
├── utils/ # 汎用ユーティリティ関数
├── hooks/ # カスタムフック(グローバルUI用のみ)
└── contexts/ # アプリ全体で使うコンテキスト
コンポーネントの責務と状態管理のルール
▶ app/:データ取得とルーティング
app/ディレクトリ(例: app/player/page.tsx)では、基本的にデータ取得(fetch)を行い、そのデータをpages/以下に渡します。ここがSSR/ISRなどの最上位責務を担います。
▶ packages/ui/*/components/{atoms,molecules,organisms}:完全にデータレスなUI提供(インタラクション、UIの振る舞い)
ボタンや入力、ラベルなどの純粋なUIのみを提供。状態は持ちません。状態やデータはすべて上位からpropsとして渡されることを前提としています。
▶ domains/{domain}/pages/:ドメインに閉じた責務
各ドメイン(player, event, など)ごとにUIを切り出します。
データを受け取って表示するだけのプレゼンテーショナルコンポーネントとして機能し、ロジックは持ちません。基本的にはatomic designのPagesに当たるコンポーネントが入ってきます。
※現状、本プロジェクトではドメイン分析は行えていないので、domain分割しておりません。
UI状態とアプリ状態を分けて考える
状態管理で最も大事なのは、「UIの状態」と「アプリケーションの状態」を分けて考えることです。
★ UI状態とは?(organismsが持つ)
- 開閉状態(isOpen)
- スライダーの現在位置(currentIndex)
- タブの選択状態
- 入力欄の一時的な状態
これらはすべて「インタラクションに起因する一時的な状態」であり、organisms内でuseState
等を使って自己完結させるのが理想です。
★ アプリケーション状態とは?(上位で持つ)
- ログイン中のユーザー情報
- APIから取得したドメインデータ
- 選択中のエンティティID
- パーミッション・ロール
これらは複数の画面やUI間で共有されるべきなので、pages/
やドメインのトップレベルで保持し、必要に応じてpropsで渡します。
contextは最小限に
状態が交差しすぎる場面ではcontextも検討されますが、基本的には使わない方針です。
- 状態の流れが見えづらくなる
- ブラックボックス化する
- 再レンダリングが広範囲になる
contextを使うのは「テーマ切り替え」「認証」「トースト通知」など、アプリ全体に影響するUIインフラレベルに限定します。
shared/hooksは?
useMediaQuery
, useClickOutside
, useScrollLock
など、UI全体で共有されるようなインタラクションヘルパーだけを shared/hooks に置きます。 それ以外のロジックやhooksは、可能な限りorganismsに閉じ込めます。
propsによる“バケツリレー”とcontextのトレードオフ
状態管理において「propsで渡すか」「contextで共有するか」はトレードオフの関係にあります。
props(バケツリレー)のメリット
- 状態の流れが明示的で追いやすい
- 依存関係が明確(何がどこで使われているかがコード上に現れる)
- テストしやすい(引数として扱える)
propsのデメリット
- コンポーネント階層が深いと渡すのが面倒になる
- 中間コンポーネントが「受け渡しだけ」の役割になりがち
contextのメリット
- 深い階層でも props を渡さず状態を共有できる
- 特定の状態を“グローバルに”扱える(例: ユーザー、テーマ)
contextのデメリット
- どこで使われているか分かりにくくなる(暗黙的な依存)
- パフォーマンスの悪化(全体再レンダリングのリスク)
- テストが難しくなる(ラップが必要、意図しない副作用)
✅基本は props での明示的なデータフローを優先し、 context は "アプリ全体で必要なUIインフラ" に限定することで、健全で保守しやすい状態管理が実現できます。
おわりに
状態管理はコンポーネント設計と密接に関わる大事な部分です。 「状態の流れが明示的であること」「責務がレイヤーごとに分離されていること」を重視することで、保守性と可読性が高く、迷いのないUI設計が実現できます。
propsによる“バケツリレー”も、設計の軸さえ明確なら立派なアーキテクチャです。contextに逃げず、まずは上から渡す構造を基本とすることで、健全なアプリケーション設計が育っていくと思っています。