Reactフォルダ構成の基本|srcフォルダの中身を理解する

Reactプロジェクトのsrcフォルダ構成を初心者向けに詳しく解説。コンポーネント、ページ、ユーティリティファイルの適切な配置方法とベストプラクティスを実例とともに紹介します。

みなさん、Reactでプロジェクトを始めていますか?

「srcフォルダの中をどう整理すればいいの?」「ファイルが増えてきて、どこに何を置けばいいか分からない」と悩んだことはありませんか?

この記事では、React初心者でも迷わないフォルダ構成の基本をお伝えします。 コンポーネントやページファイルの適切な配置方法を、実例とともに分かりやすく解説していきます。

Reactプロジェクトの基本構成

まずは、Create React Appで作られる基本的な構成から見ていきましょう。

最初に作られるファイル構成

Create React Appを実行すると、こんなファイルが作られます:

my-react-app/
├── public/
│   ├── index.html
│   ├── favicon.ico
│   └── manifest.json
├── src/
│   ├── App.js
│   ├── App.css
│   ├── App.test.js
│   ├── index.js
│   ├── index.css
│   ├── logo.svg
│   └── reportWebVitals.js
├── package.json
├── package-lock.json
└── README.md

シンプルですよね。 でも実は、この構成にはちゃんとした理由があるんです。

各ファイルの役割を理解しよう

それぞれのファイルが何をしているのか、見てみましょう:

// src/index.js - アプリのエントリーポイント
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>
);

index.jsはアプリのスタート地点です。 Reactアプリを画面に表示するための準備をしています。

// src/App.js - メインのコンポーネント
import React from 'react';
import './App.css';

function App() {
  return (
    <div className="App">
      <header>
        <h1>私のReactアプリ</h1>
      </header>
      <main>
        <p>ここにコンテンツが入ります</p>
      </main>
    </div>
  );
}

export default App;

App.jsがアプリ全体の親コンポーネントになります。 ここから他のコンポーネントを呼び出していくイメージです。

publicフォルダとsrcフォルダの違い

この2つのフォルダには、重要な違いがあります:

publicフォルダ

  • 静的なファイルを置く場所
  • HTMLファイル、画像、アイコンなど
  • webpackによる処理を受けません

srcフォルダ

  • アプリのメインコードを置く場所
  • JavaScriptファイル、CSSファイルなど
  • webpackで処理されてバンドルされます

簡単に言うと、プログラムで使うファイルはsrcに入れる、と覚えておけば大丈夫です。

プロジェクトが成長したらどうする?

最初はシンプルでも、ファイルが増えてくると整理が必要になります。 段階的にフォルダを整理していく方法を見てみましょう。

小規模プロジェクト(10ファイル程度)

コンポーネントが少ない時は、こんな感じで整理できます:

src/
├── components/
│   ├── Header.js
│   ├── Footer.js
│   └── Button.js
├── App.js
├── App.css
├── index.js
└── index.css

componentsフォルダを作って、再利用できるパーツをまとめています。 この段階では、これで十分です。

// src/components/Header.js
import React from 'react';

const Header = ({ title }) => {
  return (
    <header>
      <h1>{title}</h1>
      <nav>
        <ul>
          <li><a href="/">ホーム</a></li>
          <li><a href="/about">について</a></li>
        </ul>
      </nav>
    </header>
  );
};

export default Header;

Headerコンポーネントを作って、タイトルをpropsで受け取れるようにしました。 これで色々な場所で使い回せますね。

中規模プロジェクト(30-50ファイル)

ファイルが増えてきたら、もう少し細かく分けていきます:

src/
├── components/
│   ├── common/
│   │   ├── Header/
│   │   ├── Footer/
│   │   └── Button/
│   └── forms/
│       ├── ContactForm.js
│       └── LoginForm.js
├── pages/
│   ├── Home.js
│   ├── About.js
│   └── Contact.js
├── utils/
│   ├── api.js
│   └── helpers.js
├── App.js
└── index.js

新しく追加されたフォルダの説明

  • common: どこでも使える共通コンポーネント
  • forms: フォーム関連のコンポーネント
  • pages: ページ全体を表すコンポーネント
  • utils: 便利な関数をまとめた場所
// src/utils/api.js
const API_BASE_URL = 'https://api.example.com';

export const fetchUsers = async () => {
  try {
    const response = await fetch(`${API_BASE_URL}/users`);
    if (!response.ok) {
      throw new Error('データの取得に失敗しました');
    }
    return await response.json();
  } catch (error) {
    console.error('APIエラー:', error);
    throw error;
  }
};

API呼び出しをまとめた関数を作りました。 こうしておくと、他のコンポーネントからも使えて便利です。

// src/pages/Home.js
import React, { useState, useEffect } from 'react';
import { fetchUsers } from '../utils/api';
import Header from '../components/common/Header/Header';

const Home = () => {
  const [users, setUsers] = useState([]);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    const loadUsers = async () => {
      try {
        const userData = await fetchUsers();
        setUsers(userData);
      } catch (error) {
        console.error('データ読み込みエラー:', error);
      } finally {
        setLoading(false);
      }
    };

    loadUsers();
  }, []);

  if (loading) {
    return <div>読み込み中...</div>;
  }

  return (
    <div>
      <Header title="ホームページ" />
      <main>
        <h2>ユーザー一覧</h2>
        <p>ユーザー数: {users.length}</p>
      </main>
    </div>
  );
};

export default Home;

ページコンポーネントでは、API呼び出しやデータの管理を行います。 共通コンポーネントと組み合わせて、完成したページを作っていくイメージです。

大規模プロジェクト(50ファイル以上)

プロジェクトがさらに大きくなると、より詳細な分類が必要になります:

src/
├── components/
│   ├── common/
│   │   ├── Button/
│   │   ├── Modal/
│   │   └── Loading/
│   ├── layout/
│   │   ├── Header/
│   │   ├── Sidebar/
│   │   └── Footer/
│   └── forms/
│       ├── ContactForm/
│       └── LoginForm/
├── pages/
│   ├── Home/
│   ├── About/
│   └── Dashboard/
├── hooks/
│   ├── useApi.js
│   └── useAuth.js
├── context/
│   ├── AuthContext.js
│   └── ThemeContext.js
├── utils/
│   ├── api/
│   ├── helpers/
│   └── constants.js
├── styles/
│   ├── globals.css
│   └── variables.css
└── assets/
    ├── images/
    └── icons/

新しいフォルダの役割

  • hooks: カスタムHooksをまとめる場所
  • context: Context APIで状態管理
  • assets: 画像やアイコンなどの静的ファイル
  • styles: CSS関連ファイル

各フォルダの詳しい使い方

それぞれのフォルダを、もっと詳しく見ていきましょう。

componentsフォルダの整理方法

コンポーネントは用途によって分類するのがおすすめです。

commonフォルダ

どこでも使える汎用的なコンポーネントを入れます:

// src/components/common/Button/Button.js
import React from 'react';
import './Button.css';

const Button = ({ 
  children, 
  variant = 'primary', 
  size = 'medium', 
  onClick 
}) => {
  const className = `button button--${variant} button--${size}`;
  
  return (
    <button 
      className={className}
      onClick={onClick}
    >
      {children}
    </button>
  );
};

export default Button;

このボタンコンポーネントは、variantsizeでスタイルを変えられます。 プロジェクト全体で統一感のあるボタンが使えますね。

layoutフォルダ

ページの構造を作るコンポーネントを入れます:

// src/components/layout/Header/Header.js
import React from 'react';
import { useAuth } from '../../../hooks/useAuth';
import './Header.css';

const Header = () => {
  const { user, logout } = useAuth();

  return (
    <header className="header">
      <div className="header__logo">
        <h1>私のアプリ</h1>
      </div>
      <nav className="header__nav">
        {user ? (
          <div>
            <span>こんにちは、{user.name}さん</span>
            <button onClick={logout}>ログアウト</button>
          </div>
        ) : (
          <a href="/login">ログイン</a>
        )}
      </nav>
    </header>
  );
};

export default Header;

ヘッダーコンポーネントでは、ログイン状態によって表示を変えています。 カスタムHooksを使って、認証情報を取得しているのがポイントです。

pagesフォルダの使い方

ページ全体を表すコンポーネントを入れます。 各ページに必要な情報をまとめて管理します:

// src/pages/Dashboard/Dashboard.js
import React, { useContext } from 'react';
import { AuthContext } from '../../context/AuthContext';
import Header from '../../components/layout/Header/Header';
import StatsCards from './components/StatsCards';
import './Dashboard.css';

const Dashboard = () => {
  const { user } = useContext(AuthContext);

  if (!user) {
    return <div>ログインしてください</div>;
  }

  return (
    <div className="dashboard">
      <Header />
      <main className="dashboard__content">
        <h2>ダッシュボード</h2>
        <StatsCards userId={user.id} />
      </main>
    </div>
  );
};

export default Dashboard;

ダッシュボードページでは、認証情報をチェックしてからコンテンツを表示しています。 ページ固有のコンポーネント(StatsCards)も組み込んでいますね。

ページ固有のコンポーネント管理

大きなページでは、そのページでしか使わないコンポーネントが出てきます。 そんな時は、ページフォルダの中にcomponentsフォルダを作ると便利です:

src/pages/Dashboard/
├── Dashboard.js
├── Dashboard.css
├── components/
│   ├── StatsCards/
│   └── RecentActivity/
└── hooks/
    └── useDashboardData.js

utilsフォルダの活用法

便利な関数やヘルパーをまとめる場所です。

API関連の整理

// src/utils/api/client.js
const API_BASE_URL = process.env.REACT_APP_API_URL;

class ApiClient {
  async request(endpoint, options = {}) {
    const url = `${API_BASE_URL}${endpoint}`;
    const config = {
      headers: {
        'Content-Type': 'application/json',
      },
      ...options,
    };

    // 認証トークンがあれば追加
    const token = localStorage.getItem('authToken');
    if (token) {
      config.headers.Authorization = `Bearer ${token}`;
    }

    try {
      const response = await fetch(url, config);
      
      if (!response.ok) {
        throw new Error(`APIエラー: ${response.status}`);
      }
      
      return await response.json();
    } catch (error) {
      console.error('API呼び出し失敗:', error);
      throw error;
    }
  }

  get(endpoint) {
    return this.request(endpoint, { method: 'GET' });
  }

  post(endpoint, data) {
    return this.request(endpoint, {
      method: 'POST',
      body: JSON.stringify(data),
    });
  }
}

export default new ApiClient();

ApiClientクラスを作って、API呼び出しをまとめました。 認証トークンの管理やエラーハンドリングも含まれています。

// src/utils/api/users.js
import apiClient from './client';

export const getUsers = () => {
  return apiClient.get('/users');
};

export const getUser = (id) => {
  return apiClient.get(`/users/${id}`);
};

export const createUser = (userData) => {
  return apiClient.post('/users', userData);
};

ユーザー関連のAPI呼び出しを関数でまとめています。 コンポーネントからは、これらの関数を呼び出すだけで済みます。

ヘルパー関数の整理

// src/utils/helpers/validation.js
export const validateEmail = (email) => {
  const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
  return emailRegex.test(email);
};

export const validatePassword = (password) => {
  return {
    isValid: password.length >= 8,
    hasUpperCase: /[A-Z]/.test(password),
    hasLowerCase: /[a-z]/.test(password),
    hasNumbers: /\d/.test(password),
  };
};

バリデーション関数をまとめました。 フォームで入力チェックをする時に、とても便利です。

// src/utils/helpers/formatting.js
export const formatDate = (date) => {
  return new Intl.DateTimeFormat('ja-JP', {
    year: 'numeric',
    month: 'long',
    day: 'numeric',
  }).format(new Date(date));
};

export const formatCurrency = (amount) => {
  return new Intl.NumberFormat('ja-JP', {
    style: 'currency',
    currency: 'JPY',
  }).format(amount);
};

日付や通貨のフォーマット関数です。 表示用の値を統一できて、見た目が整います。

hooksフォルダの使い方

カスタムHooksで、ロジックを再利用しやすくします。

// src/hooks/useApi.js
import { useState, useEffect, useCallback } from 'react';

export const useApi = (apiFunction) => {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  const fetchData = useCallback(async () => {
    try {
      setLoading(true);
      setError(null);
      const result = await apiFunction();
      setData(result);
    } catch (err) {
      setError(err.message);
    } finally {
      setLoading(false);
    }
  }, [apiFunction]);

  useEffect(() => {
    fetchData();
  }, [fetchData]);

  return { data, loading, error, refetch: fetchData };
};

useApiHooksを作りました。 API呼び出しの状態管理(読み込み中、エラー、データ)を自動でやってくれます。

// コンポーネントでの使用例
import { useApi } from '../hooks/useApi';
import { getUsers } from '../utils/api/users';

const UserList = () => {
  const { data: users, loading, error } = useApi(getUsers);

  if (loading) return <div>読み込み中...</div>;
  if (error) return <div>エラー: {error}</div>;

  return (
    <ul>
      {users?.map(user => (
        <li key={user.id}>{user.name}</li>
      ))}
    </ul>
  );
};

カスタムHooksを使うと、コンポーネントがとてもシンプルになりますね。

contextフォルダの管理

アプリ全体で共有する状態を管理します。

// src/context/AuthContext.js
import React, { createContext, useState, useEffect } from 'react';
import { getCurrentUser } from '../utils/api/auth';

export const AuthContext = createContext({
  user: null,
  setUser: () => {},
  loading: true,
});

export const AuthProvider = ({ children }) => {
  const [user, setUser] = useState(null);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    const initializeAuth = async () => {
      try {
        const token = localStorage.getItem('authToken');
        if (token) {
          const currentUser = await getCurrentUser();
          setUser(currentUser);
        }
      } catch (error) {
        console.error('認証初期化エラー:', error);
        localStorage.removeItem('authToken');
      } finally {
        setLoading(false);
      }
    };

    initializeAuth();
  }, []);

  const value = {
    user,
    setUser,
    loading,
  };

  return (
    <AuthContext.Provider value={value}>
      {children}
    </AuthContext.Provider>
  );
};

認証状態をアプリ全体で共有するためのContextです。 ページをリロードしても、ログイン状態を保持できます。

// src/hooks/useAuth.js
import { useContext, useCallback } from 'react';
import { AuthContext } from '../context/AuthContext';
import { loginUser, logoutUser } from '../utils/api/auth';

export const useAuth = () => {
  const { user, setUser, loading } = useContext(AuthContext);

  const login = useCallback(async (email, password) => {
    try {
      const result = await loginUser(email, password);
      setUser(result.user);
      localStorage.setItem('authToken', result.token);
      return { success: true };
    } catch (error) {
      return { success: false, error: error.message };
    }
  }, [setUser]);

  const logout = useCallback(() => {
    setUser(null);
    localStorage.removeItem('authToken');
    logoutUser();
  }, [setUser]);

  return {
    user,
    login,
    logout,
    loading,
    isAuthenticated: !!user,
  };
};

認証関連の操作をまとめたカスタムHooksです。 コンポーネントからは、このHooksを使ってログイン・ログアウトができます。

良いフォルダ構成のルール

効果的なフォルダ構成には、いくつかのポイントがあります。

一貫性のある命名規則

ファイル名の付け方

  • コンポーネント: UserProfile.js(PascalCase)
  • フォルダ名: commonuserManagement(camelCase)
  • CSS ファイル: UserProfile.css(コンポーネント名と同じ)
// ✅ 良い例
const goodExamples = [
  'UserProfile.js',      // 分かりやすい
  'ContactForm.js',      // 用途が明確
  'NavigationMenu.js',   // 具体的
];

// ❌ 避けるべき例
const badExamples = [
  'Component1.js',       // 何のコンポーネントか不明
  'utils.js',           // 用途が曖昧
  'userprofile.js',     // 単語の区切りがない
];

適切な階層の深さ

フォルダの階層は、深すぎると使いにくくなります。 3-4層までに抑えるのがおすすめです。

// ✅ 適切な深さ
const goodStructure = `
src/components/common/Button/Button.js
`;

// ❌ 深すぎる例
const tooDeep = `
src/components/features/user/profile/settings/personal/form/inputs/TextInput.js
`;

関連ファイルは近くに配置

関連するファイルは、同じフォルダにまとめましょう。

// ✅ 関連ファイルがまとまっている
const wellOrganized = `
src/components/common/Button/
├── Button.js        // メインコンポーネント
├── Button.css       // スタイル
├── Button.test.js   // テスト
└── index.js         // エクスポート用
`;

避けるべきアンチパターン

よくある間違いを見ていきましょう。

深すぎる階層構造

// ❌ 悪い例
const tooComplicated = `
src/components/features/user/profile/settings/personal/information/display/UserName.js
`;

// ✅ 改善後
const simplified = `
src/components/user/UserNameDisplay.js
`;

異なる関心事の混在

// ❌ 悪い例:utilsフォルダに色々混ざっている
const mixedConcerns = `
src/utils/
├── formatDate.js      // フォーマット関連
├── UserProfile.js     // コンポーネント(場所が間違い)
├── apiCall.js         // API関連
└── validation.js      // バリデーション関連
`;

// ✅ 改善後:適切に分類
const properSeparation = `
src/components/UserProfile.js
src/utils/formatting.js
src/services/api.js
src/utils/validation.js
`;

一貫性のない命名

// ❌ 悪い例:バラバラな命名規則
const inconsistentNames = [
  'userProfile.js',      // camelCase
  'User-List.js',        // kebab-case + PascalCase
  'CONTACT_FORM.js',     // UPPER_CASE
];

// ✅ 改善後:統一されたPascalCase
const consistentNames = [
  'UserProfile.js',
  'UserList.js',
  'ContactForm.js',
];

プロジェクト規模別の推奨構成

プロジェクトの大きさに応じて、最適な構成は変わります。

小規模プロジェクト(20コンポーネント以下)

シンプルで分かりやすい構成がおすすめです:

src/
├── components/
│   ├── Header.js
│   ├── Footer.js
│   ├── Button.js
│   └── Modal.js
├── pages/
│   ├── Home.js
│   ├── About.js
│   └── Contact.js
├── utils/
│   ├── api.js
│   └── helpers.js
├── App.js
└── index.js

小規模プロジェクトの特徴

  • ファイル数が少ないので、検索しやすい
  • 設定の手間が少ない
  • チーム開発でも理解しやすい

注意点

  • コンポーネントが20個を超えたら構成を見直す
  • 同じ名前のファイルが出現したら分類を検討
  • 機能が複雑になったら早めに整理する

大規模プロジェクト(100コンポーネント以上)

機能ベースでの分類が重要になります:

src/
├── components/
│   ├── common/
│   ├── layout/
│   └── domain/
├── pages/
│   ├── auth/
│   ├── dashboard/
│   └── user/
├── features/
│   ├── authentication/
│   ├── userManagement/
│   └── productCatalog/
├── shared/
│   ├── hooks/
│   ├── context/
│   ├── utils/
│   └── services/
├── assets/
├── styles/
└── tests/

大規模プロジェクトのポイント

  • 機能やドメインで明確に分ける
  • 共有リソースはsharedフォルダに分離
  • 各フォルダの責任範囲を明確にする
  • チーム内でのルールを文書化する

便利なツールの活用

// .eslintrc.json での import パス制御
{
  "rules": {
    "import/no-restricted-paths": [
      "error",
      {
        "zones": [
          {
            "target": "./src/components/common",
            "from": "./src/features"
          }
        ]
      }
    ]
  }
}

ESLintで、間違ったインポートを防げます。 commonコンポーネントがfeaturesに依存しないようにチェックしています。

まとめ

Reactプロジェクトの適切なフォルダ構成について、詳しく解説しました。

覚えておきたいポイント

  • プロジェクトの規模に応じて構成を選ぶ
  • 一貫性のある命名規則を守る
  • 関連するファイルは近くに配置する
  • 深すぎる階層は避ける

基本的なフォルダの役割

  • components: 再利用可能なコンポーネント
  • pages: ページレベルのコンポーネント
  • hooks: カスタムHooks
  • context: グローバルな状態管理
  • utils: ヘルパー関数とユーティリティ

成長に合わせた構成の発展

  1. 小規模: シンプルで分かりやすく
  2. 中規模: カテゴリ別に分類
  3. 大規模: 機能ベースで整理

最も大切なのは、チーム全体で一貫した構成を維持することです。 定期的に見直しを行い、プロジェクトの成長に合わせて最適化していきましょう。

ぜひこの記事を参考にして、効率的で保守しやすいReactプロジェクトを作ってみてくださいね!

関連記事