React Native Course
Navigation

Expo公式エンジニア Kadi Kraman 氏のフォルダ構造戦略を、実践ルールとともに日本語で解説します。

【保存版】Expoアプリのフォルダ構造を明瞭かつスケーラブルに整理する方法

はじめに

React Native / Expoでアプリ開発をしていて、こんな悩みはありませんか?

  • ファイルがどこにあるかわからない
  • プロジェクトが大きくなるにつれてコードが散らかる
  • チームメンバーとコードの置き場所で揉める
  • AIにコード生成させても、どこに配置すべきかわからない

実は、適切に整理されたフォルダ構造は生産性に大きな影響を与えます。

整頓されたオフィスが最高の仕事をするための環境を整えるように、すべてのファイルが適切な場所に配置されたコードベースも同様です。

今回は、Expo公式エンジニアのKadi Kraman氏が提唱する「実績のあるフォルダ構造戦略」を日本語でわかりやすく解説します。

1. /src フォルダを使おう

なぜ /src が大事なのか

アプリケーションのコードと設定ファイルを分離することが、コードベースをクリーンに保つ基本です。

Before(/srcを使わない場合)

├── app/
├── assets/
├── components/
├── scripts/
├── hooks/
├── constants.ts
├── theme.ts
├── app.json
├── eas.json
└── package.json

After(/srcを使う場合)

├── assets/
├── scripts/
├── src/
│   ├── app/
│   ├── components/
│   ├── hooks/
│   ├── constants.ts
│   └── theme.ts
├── app.json
├── eas.json
└── package.json

ポイント: /src フォルダを使うと、アプリケーションコードと設定ファイルの区別が一目瞭然になります。

Expo Routerは /app/src/app の両方をサポートしているので、フォルダを移動してバンドラを再起動するだけで切り替えられます。

2. /components フォルダの作り方

ファイルベースルーティングでは、/app フォルダ内のすべてのファイルが新しいルートを作成するため、再利用可能なコンポーネントは別の場所に置く必要があります

ファイル命名規則(2つの考え方)

クラシック

  • 例:Button.tsx
  • 特徴:コンポーネント名と完全に同じ

モダン

  • 例:button.tsx
  • 特徴:ケバブケース(SDK 55推奨)

どちらも有効ですが、一貫性を保つことが重要です。

複数ファイルに分割する場合

└── src/
    └── components/
        ├── table/
        │   ├── components/
        │   │   ├── row.tsx
        │   │   └── cell.tsx
        │   └── index.tsx
        └── button.tsx

index.tsx を使うことで、インポートパスが変わらないのがメリットです。

// どちらも同じパスでインポートできる
import { Table } from "@components/table";
import { Button } from "@components/button";

3. /screens フォルダで大規模アプリに対応

アプリが成長すると、ページ固有の複雑なUIコードがルートファイル内に散乱しがちです。

解決策:/screens フォルダ

// src/app/index.tsx
import { Home } from "@screens/home";

export default function HomeScreen() {
  // URLパラメータの取得など、ルート固有の処理
  return <Home />;
}

フォルダ構造

└── src/
    ├── app/
    │   ├── index.tsx
    │   ├── settings.tsx
    │   └── _layout.tsx
    └── screens/
        ├── home/
        │   ├── components/
        │   │   └── timeline.tsx
        │   └── index.tsx
        └── settings.tsx

メリット:

  • UIロジックとルート定義を分離できる
  • 同じスクリーンを複数のルートで簡単に再利用できる
  • ページ固有のコンポーネントを整理しやすい

4. /utils/hooks でユーティリティを整理

/utils フォルダ

日付フォーマッタ、通貨コンバータなどの小さなスタンドアロンユーティリティを置く場所です。

└── src/
    └── utils/
        ├── format-date.ts
        └── pluralize.ts

/hooks フォルダ

React (Native) では再利用可能なカスタムフックを頻繁に作成するため、専用フォルダがあると便利です。

└── src/
    └── hooks/
        ├── use-app-state.ts
        └── use-theme.ts

💡 Kadi氏のジョーク 「私はコード整理にこんまりメソッドを使っています。 このコードはときめきますか? YES → /src NO → /utils」

5. /server フォルダでサーバーコードを分離

Expo RouterのAPIルート機能を使うと、React Nativeのコードベース内に直接サーバーコードを記述できます。

なぜ分離が必要?

  • APIコードはサーバーサイド環境で実行される
  • 機密性の高い環境変数(process.env.MY_SECRET など)を使用できる
  • フロントエンドとは異なるランタイムで動作する

推奨構造

└── src/
    ├── app/
    │   ├── api/
    │   │   ├── user+api.ts
    │   │   └── settings+api.ts
    │   └── index.tsx
    └── server/
        ├── auth.ts
        └── db.ts

ポイント: APIルートは /app/api/ フォルダにまとめると、/api/user のようなエンドポイントになり、管理しやすくなります。

6. プラットフォーム固有コードの管理

複数のプラットフォーム(Web、iOS、Android)をターゲットにする場合、ファイル拡張子を利用して実装を分離できます。

例:Web用とネイティブ用で異なるチャートコンポーネント

└── components/
    ├── bar-chart.tsx       # デフォルト(iOS/Android用)
    └── bar-chart.web.tsx   # Web用

使い方

// 拡張子なしでインポート
import { BarChart } from "@components/bar-chart";

Metroが自動的にプラットフォームに応じたファイルを選択してくれます。

注意点

  1. propsは両方のコンポーネントで同一であるべき
  2. デフォルト版(拡張子なし)のコンポーネントが常に必要
  3. サポートされている拡張子:.web.native.ios.android

7. スタイルは同じファイルに

❌ 古いアプローチ(非推奨)

└── components/
    ├── button.tsx
    └── button.styles.tsx

✅ 現代的なアプローチ(推奨)

// button.tsx
export function Button() {
  return <View style={styles.container}>...</View>;
}

const styles = StyleSheet.create({
  container: {
    // ...
  },
});

理由: スタイルがコンポーネントと同じファイルにあると、見やすく作業しやすくなります。

8. ユニットテストの配置

推奨:テスト対象ファイルの隣に配置

└── src/
    └── utils/
        ├── format-date.ts
        └── format-date.test.ts

メリット: どのファイルがテストされているかが一目でわかり、テストカバレッジの確認が容易になります。

完成形:理想的なフォルダ構造

すべてのルールを適用した最終形がこちらです:

├── assets/
├── scripts/
├── src/
│   ├── app/
│   │   ├── api/                    # APIルート
│   │   │   ├── event+api.ts
│   │   │   └── user+api.ts
│   │   ├── _layout.tsx
│   │   ├── _layout.web.tsx         # Web用レイアウト
│   │   ├── index.tsx
│   │   ├── events.tsx
│   │   └── settings.tsx
│   ├── components/                 # 再利用可能コンポーネント
│   │   ├── table/
│   │   │   ├── cell.tsx
│   │   │   └── index.tsx
│   │   ├── bar-chart.tsx
│   │   ├── bar-chart.web.tsx
│   │   └── button.tsx
│   ├── screens/                    # 画面コンポーネント
│   │   ├── home/
│   │   │   ├── card.tsx
│   │   │   └── index.tsx
│   │   ├── events.tsx
│   │   └── settings.tsx
│   ├── server/                     # サーバーサイドコード
│   │   ├── auth.ts
│   │   └── db.ts
│   ├── utils/                      # ユーティリティ関数
│   │   ├── format-date.ts
│   │   ├── format-date.test.ts
│   │   └── pluralize.ts
│   └── hooks/                      # カスタムフック
│       ├── use-app-state.ts
│       └── use-theme.ts
├── app.json
├── eas.json
└── package.json

まとめ

  • /src → アプリコードと設定を分離
  • /components → 再利用可能なUIコンポーネント
  • /screens → 画面固有のコンポーネント
  • /utils → ユーティリティ関数
  • /hooks → カスタムフック
  • /server → サーバーサイドコード
  • /api → APIルートをまとめる

いくつかのシンプルなルールを適用するだけで、コードベースは大規模になってもはるかに管理しやすくなります。

ぜひ、あなたのExpoプロジェクトでも試してみてください!

参考