こんにちは。中澤です。 わたしは1年ほど前から本格的にフロントエンド開発について学び始め、先日社内向けにwebアプリケーションをリリースしました。 一区切りついたタイミングで、リリースされたアプリケーションを振り返りたいと思い、この記事では特にディレクトリ構成について焦点をあてています。
前提として、リリースされたアプリケーションの特徴は以下のようなものになります。
- 社内向けアプリケーション
- SPA
- React Router の framework mode を採用
実装したアプリケーションのディレクトリ構成を振り返る
さっそく本アプリケーションのディレクトリ構成について振り返ってみます。
app - assets # エラーや404画像ファイルなど、静的リソース - e2e # Playwright によるE2Eテスト関連ファイル - features # 機能ごとのディレクトリ - icons # SVGアイコンを返す関数コンポーネント - lib # アプリケーション全体で使う設定済みライブラリ(axios、queryClientなど) - mocks # MSW(Mock Service Worker)によるAPIモック - react-utils # React関連の汎用ユーティリティ - ui # Button、Input、Radioなど再利用可能な基本UIコンポーネント
また、features以下に解説したい要素があるので、このフォルダをもう少し掘り下げてみます。
app/features
- featureA
- components # featureA専用のコンポーネント
- Layout.tsx
- Form.tsx
- routes # featureAのルート定義
- index
- index.tsx
- clientLoader.ts
- edit
- index.tsx
- clientLoader.ts
- clientAction.ts
- misc # 認証画面など、特定機能に属さない画面
- routes
- Login.tsx
- Home.tsx
- NotFound.tsx
このアプリケーションではルーティングライブラリとしてReactRouterを使っています。 そのため、各フォルダ内のroutes内のファイルを手厚く記載していますが、これについての説明は後述します。
featuresについて
ここでfeatureAと表現されているものについて、イメージしやすいように例をだしておきます。 たとえば郵便局のサイトの、「郵便局を探す」「集荷申し込み」「海外に送る」などの単位が機能になります。 つまり、featureは運用されていく過程で、追加されたり、削除されたりする可能性が高い単位という性質を持ちます。
本アプリケーションは、今後もいくつかの機能追加を予定しているため、このようなfeature単位での追加・削除が意図された設計となっています。
React Router の Route モジュールが抱える課題
これは、ルートファイルから参照されるファイルを指し、このファイルにはデータロードやアクションなど様々な記述が含まれます。
Route modules are the foundation of React Router's framework features, they define:
- automatic code-splitting
- data loading
- actions
- revalidation
- error boundaries
- and more
そのため、ルートモジュールはファイルが長くなり、保守が難しくなっていく課題を抱えていました。
Re-exporting 構文による責務の分離
この課題に対し、"Re-exporting" 構文を使ってファイルを分割しています。
// routes/edit/index.tsx(エントリーポイント) export { default } from './EditPage'; export { clientLoader } from './clientLoader'; export { clientAction } from './clientAction';
// routes/edit/EditPage.tsx(UIロジック) export default function EditPage() { // コンポーネント実装 }
// routes/edit/clientLoader.ts(データ取得) import type { ClientLoaderFunctionArgs } from 'react-router'; export async function clientLoader({ params }: ClientLoaderFunctionArgs) { // データ取得処理 const data = await fetchData(params.id); return { data }; }
// routes/edit/clientAction.ts(更新処理) import type { ClientActionFunctionArgs } from 'react-router'; export async function clientAction({ request }: ClientActionFunctionArgs) { const formData = await request.formData(); // バリデーション、API呼び出しなど return { success: true }; }
この対応により、ルートモジュールの肥大化を抑え、保守性が高い状態にしています。
まとめ
この記事では、リリースした社内向けアプリケーションのディレクトリ構成と、React Routerのルートモジュール周りの実装について考察しました。
実は、この記事を書くきっかけとなったのは、先日参加した外部の勉強会で、 「ルートモジュールの肥大化にどう対応しているか?」という話題がでたことでした。 このファイル分割はテックリードのメンバがディレクトリ構成周りを整理した際にあわせて導入していただいていたので、上述したようなファイル分割のテクニックを理解しておらず、その場ではうまく説明することができませんでした。
外部勉強会に参加すると、こういった理解が甘い部分が見つかるきっかけにもなるのがいいですね。
最後までお読みいただきありがとうございました。
JMDCでは、ヘルスケア領域の課題解決に一緒に取り組んでいただける方を積極採用中です!フロントエンド /バックエンド/ データベースエンジニア等、様々なポジションで募集をしています。詳細は下記の募集一覧からご確認ください。 hrmos.co まずはカジュアルにJMDCメンバーと話してみたい/経験が活かせそうなポジションの話を聞いてみたい等ございましたら、下記よりエントリーいただけますと幸いです。 hrmos.co ★最新記事のお知らせはぜひ X(Twitter)、またはBlueskyをご覧ください! Tweets by jmdc_tech twitter.com bsky.app