Reactはコンポーネントの設計自由度が高い反面、プロジェクトが大規模化すると「どこに何を配置すべきか」「状態管理をどう整理するか」という課題に直面します。Bulletproof Reactは、GitHubスター数34,000超の実践的アーキテクチャガイドで、こうした課題に対する体系的な解決策を提示しています。

Bulletproof Reactの概要と設計思想

Bulletproof Reactは、alan2207氏が公開しているオープンソースのReactアーキテクチャリファレンスです。MITライセンスで公開されており、TypeScript 92%・JavaScript 6.6%で構成されています。

「堅牢(Bulletproof)」なReactアプリケーションの条件として、公式リポジトリでは以下の9つの原則が掲げられています。

原則内容
Easy to get started with新しいメンバーがすぐに開発に参加できる
Simple to understand and maintainコードの見通しが良く保守しやすい
Uses the right tools for the right problems課題に適したツールを選定している
Clean boundaries between different parts責任の分離が明確である
Everyone on the team follows the same patternsチーム全体で統一された規約がある
Secureセキュリティへの配慮がされている
Performantパフォーマンスが最適化されている
Scalable機能追加に伴うコード膨張に耐えられる
Issues are caught as early as possible問題を早期に発見できる仕組みがある

公式リポジトリでは、これらの原則を実装レベルで示すサンプルアプリケーションとドキュメント群が提供されています。

feature-basedディレクトリ構成の全体像

Bulletproof Reactが推奨するディレクトリ構成は「feature-based」と呼ばれるパターンです。従来のAtomic Designのように「Atoms / Molecules / Organisms」と粒度で分類するのではなく、ビジネスドメインの機能単位でコードを整理します。

ルートレベルの構成(src/配下)

src/
├── app/           # ルーティング、メインコンポーネント、プロバイダー
├── assets/        # 画像やフォントなどの静的ファイル
├── components/    # アプリ全体で共有するUIコンポーネント
├── config/        # 環境変数やグローバル設定
├── features/      # 機能単位のモジュール群(後述)
├── hooks/         # アプリ全体で共有するカスタムフック
├── lib/           # 外部ライブラリの設定済みラッパー
├── stores/        # グローバルな状態ストア
├── testing/       # テストユーティリティとモック
├── types/         # アプリ全体で共有する型定義
└── utils/         # 汎用ユーティリティ関数

features/配下の内部構成

各featureは独立したモジュールとして、以下のサブディレクトリを持ちます。

src/features/awesome-feature/
├── api/           # そのfeature専用のAPI定義とカスタムフック
├── assets/        # そのfeature固有の静的ファイル
├── components/    # そのfeatureに閉じたUIコンポーネント
├── hooks/         # そのfeature専用のカスタムフック
├── stores/        # そのfeature用の状態ストア
├── types/         # そのfeature内で使う型定義
└── utils/         # そのfeature固有のユーティリティ

依存関係の制約ルール

feature-basedアーキテクチャで最も重要なのは、モジュール間の依存方向を一方向に保つことです。

  • features/Afeatures/Bを直接インポートしない
  • components/(共有層)はfeatures/を参照しない
  • lib/features/を参照しない
  • feature内部のモジュールはindex.ts経由で公開APIを制御する

この制約により、各featureを独立して開発・テスト・削除できる構造が保たれます。

Atomic Designとの構造比較

Bulletproof Reactのfeature-basedアプローチと、従来のAtomic Designを比較すると、設計判断の基準が異なることがわかります。

観点Atomic DesignBulletproof React(feature-based)
分類基準UIの粒度(Atom → Molecule → Organism)ビジネスドメインの機能単位
コンポーネント配置の判断「このUIはAtomかMoleculeか?」「この部品はどの機能に属するか?」
判断の曖昧さ粒度の境界が開発者によって異なりやすい機能の所属先は比較的明確
スケーラビリティ大規模化すると各層が肥大化しやすいfeatureごとに分割されるため肥大化しにくい
削除・リファクタリング依存関係が複雑になりやすいfeature単位で安全に削除可能
学習コスト低〜中(概念は直感的)低(フォルダ名から機能が推測できる)

Atomic Designが「UIの再利用性」を重視するのに対し、feature-basedは「機能の凝集度と独立性」を優先しています。どちらが優れているかではなく、プロジェクトの規模とチーム構成に応じて選択する判断が求められます。

プロジェクト標準とコード品質の統一

Bulletproof Reactでは、チーム全体でコード品質を揃えるための標準ツールセットが提案されています。

ESLintによる静的解析

.eslintrc.cjsで独自のルールセットを構成し、以下を自動検出します。

  • 未使用変数や到達不能コード
  • React Hooksのルール違反
  • feature間の不正なインポート(後述のimport制約と連動)

Prettierによるフォーマット統一

.prettierrcでフォーマットルールを定義し、IDEの「保存時に自動フォーマット」機能と組み合わせます。フォーマットエラーは構文エラーの兆候としても機能します。

TypeScriptによる型安全性

TypeScriptの型チェックはビルド時に実行され、ESLintだけでは検出できないランタイムの不整合を事前に防ぎます。リファクタリング時には型定義を先に更新し、TypeScriptエラーを手がかりに影響範囲を特定する手法が推奨されています。

Huskyによるコミット前チェック

Gitフックを活用し、コミット前にリント・フォーマット・型チェックを自動実行します。不適切なコードがリポジトリに混入するリスクを低減します。

絶対インポートとファイル命名規則

tsconfig.json@/*パスエイリアスを設定し、深い相対パスの問題を解消します。

{
  "compilerOptions": {
    "baseUrl": ".",
    "paths": {
      "@/*": ["./src/*"]
    }
  }
}

ファイル名はKEBAB_CASE(user-profile.tsx)で統一し、ESLintのcheck-fileプラグインで強制できます。

// .eslintrc.cjs
'check-file/filename-naming-convention': [
  'error',
  { '**/*.{ts,tsx}': 'KEBAB_CASE' }
]

API層の設計パターン

Bulletproof Reactでは、APIとのやり取りを3層構造で整理しています。

1. API Clientの単一インスタンス化

アプリケーション全体で共有するHTTPクライアントをsrc/lib/api-client.tsに定義します。axios、ky、fetch APIのラッパーなどが候補です。

// src/lib/api-client.ts
import Axios from 'axios';

export const apiClient = Axios.create({
  baseURL: import.meta.env.VITE_API_URL,
  headers: {
    'Content-Type': 'application/json',
  },
});

apiClient.interceptors.response.use(
  (response) => response.data,
  (error) => {
    // エラー通知やトークンリフレッシュのロジック
    return Promise.reject(error);
  }
);

2. feature単位のAPI定義

各featureのapi/ディレクトリにリクエスト関数と型を定義します。

// src/features/discussions/api/get-discussions.ts
import { apiClient } from '@/lib/api-client';
import { Discussion } from '../types';

type GetDiscussionsParams = {
  page?: number;
};

export const getDiscussions = (
  params: GetDiscussionsParams
): Promise<Discussion[]> => {
  return apiClient.get('/discussions', { params });
};

3. React Queryによるデータ取得フック

API関数をReact Queryのカスタムフックでラップし、キャッシュ・リフェッチ・ローディング状態を管理します。

// src/features/discussions/api/use-discussions.ts
import { useQuery } from '@tanstack/react-query';
import { getDiscussions } from './get-discussions';

export const useDiscussions = (page?: number) => {
  return useQuery({
    queryKey: ['discussions', { page }],
    queryFn: () => getDiscussions({ page }),
  });
};

この構造により、APIエンドポイントの変更が各featureのapi/に閉じ込められ、影響範囲が限定されます。

状態管理の分類と推奨ライブラリ

Bulletproof Reactでは、状態を5つのカテゴリに分類し、それぞれに適したツールを選択する方針です。

状態の5分類と推奨ツール

カテゴリ用途推奨ツール
コンポーネント状態フォームの入力値、開閉状態などuseState / useReducer
アプリケーション状態モーダル表示、テーマ切替などZustand / Jotai / Context + Hooks
サーバーキャッシュ状態APIから取得したデータの保持TanStack Query / SWR / Apollo Client
フォーム状態フォーム全体のバリデーションと送信React Hook Form + Zod
URL状態ページネーション、フィルター条件React Router / useSearchParams

状態をローカルに保つ原則

「まずコンポーネント状態で管理し、必要になった時点でグローバルに昇格させる」というアプローチが推奨されています。不必要なグローバル状態は再レンダリングの原因となり、パフォーマンス低下を招きます。

サーバー状態とクライアント状態の分離

従来はReduxで全ての状態を一元管理するケースが多くありましたが、Bulletproof ReactではサーバーデータにはTanStack Query(旧React Query)やSWRなどの専用ライブラリを使い、クライアント側のUI状態とは明確に分離することを推奨しています。

コンポーネント設計とスタイリング戦略

コンポーネント設計5原則

Bulletproof Reactが示すコンポーネント設計の指針は以下の5点です。

  1. コロケーション: コンポーネント・関数・スタイル・状態を、使用箇所のできるだけ近くに配置する
  2. 巨大コンポーネントの回避: ネストしたレンダリング関数を含む大きなコンポーネントは分割する
  3. コードの一貫性: PascalCase命名、Prettier、ESLintで規約を自動化する
  4. Props数の制限: Propsが増えすぎたらcompositionパターン(children / slots)で解決する
  5. 共有コンポーネントの抽象化: サードパーティUIコンポーネントはラップして使い、将来の差し替えに備える

コンポーネントライブラリの選択肢

種別ライブラリ特徴
フル機能型Chakra UI開発体験重視、アクセシビリティ標準装備
フル機能型MUIMaterial Design実装、最大級のコンポーネント数
フル機能型MantineモダンなAPI設計、豊富なhooks
フル機能型AntD管理画面向け、多機能だがスタイルの上書きに制約あり
ヘッドレス型Radix UIスタイルなしのプリミティブ、アクセシビリティ標準準拠
ヘッドレス型Headless UITailwind CSS公式チームが開発
ヘッドレス型react-ariaAdobe製、WAI-ARIAパターンの完全実装
ヘッドレス型Ark UIマルチフレームワーク対応

スタイリングの推奨アプローチ

Bulletproof Reactが挙げるスタイリングソリューションは以下のとおりです。

  • Tailwind CSS: ユーティリティファーストCSS。React Server Componentsとの相性が良い
  • CSS Modules: スコープ付きCSSで名前衝突を防止
  • Vanilla Extract: TypeScriptでCSSを記述するゼロランタイム方式
  • Panda CSS: ゼロランタイムかつユーティリティファースト

コンポーネント配布型のアプローチとして、ShadCN UIはRadix UIベースのコピー&ペースト型コンポーネント集で、プロジェクトに直接取り込んでカスタマイズできる点が特徴です。

テスト戦略とツール選定

テストの3層構造

目的推奨ツール
ユニットテスト個別関数やコンポーネントの動作検証Vitest + Testing Library
統合テスト複数コンポーネントの連携確認Vitest + Testing Library + MSW
E2Eテストユーザー操作のシナリオ検証Playwright

統合テストへの注力

Bulletproof Reactでは、ユニットテストだけに偏らず、統合テストに注力することを推奨しています。個々の部品が正しく動いていても、結合時に問題が発生するケースは多く、統合テストがアプリケーション全体の信頼性を最も効率よく高めるためです。

MSWによるAPIモック

Mock Service Worker(MSW)は、Service Worker層でHTTPリクエストをインターセプトし、実際のサーバーなしでAPIレスポンスを再現します。テストとStorybook両方で同じモック定義を共有できます。

// src/testing/mocks/handlers.ts
import { http, HttpResponse } from 'msw';

export const handlers = [
  http.get('/api/discussions', () => {
    return HttpResponse.json([
      { id: '1', title: 'First Discussion' },
      { id: '2', title: 'Second Discussion' },
    ]);
  }),
];

エラーハンドリングの設計パターン

APIエラーのインターセプト

API Clientのインターセプターでエラーを一元処理し、以下を自動化します。

  • トースト通知によるユーザーへのフィードバック
  • 401レスポンス時の自動ログアウト
  • リフレッシュトークンによる認証の再試行

Error Boundaryによる局所化

ReactのErrorBoundaryを活用して、エラーの影響範囲を限定します。アプリ全体に1つだけ配置するのではなく、feature単位やページ単位で複数設置することで、一部の障害が全体を停止させない構造を作れます。

本番環境でのエラー追跡

Sentryなどの監視ツールを導入し、ソースマップをアップロードすることで、本番環境のエラーをソースコード上のどの行で発生したか特定できます。

セキュリティ設計:認証と認可

認証(Authentication)

JWTを用いた認証フローでは、トークンの保存先が重要です。

保存先セキュリティ補足
アプリケーション状態(メモリ)最も安全ページリロードで失われる
HttpOnly Cookie推奨JavaScriptからアクセス不可、CSRF対策は別途必要
localStorage非推奨XSS攻撃でトークンが窃取されるリスクがある

認可(Authorization)

アクセス制御には2つのアプローチがあります。

  • RBAC(ロールベース): USER、ADMIN等のロールに権限を紐付ける。シンプルな権限管理に向く
  • PBAC(パーミッションベース): リソースレベルで細かく制御する。「自分のコメントだけ削除可能」のような条件付きアクセスに対応

フロントエンド側の認可チェックはあくまでUI制御であり、サーバー側でも必ず同等のチェックを実装する必要があります。

Next.js App Routerとの統合

Bulletproof ReactはReact SPAを前提とした設計ですが、Next.js App Routerのプロジェクトにもfeature-based構成を適用できます。

ルーティング層の分離

App Routerではapp/ディレクトリがルーティングを担うため、src/features/にビジネスロジックとUIを配置し、app/配下のServer Componentからfeatureのコンポーネントを呼び出す構成が有効です。

project-root/
├── app/                          # Next.js App Router(ルーティング層)
│   ├── layout.tsx
│   ├── page.tsx
│   └── discussions/
│       ├── page.tsx              # Server Component
│       └── [id]/
│           └── page.tsx
└── src/
    ├── features/
    │   └── discussions/
    │       ├── api/
    │       ├── components/
    │       ├── hooks/
    │       └── types/
    ├── components/               # 共有UIコンポーネント
    └── lib/                      # ライブラリ設定

Server ComponentとClient Componentの境界

feature内のコンポーネントでuseState、useEffectなどのReact Hooksを使用する場合は'use client'ディレクティブが必要です。データフェッチをServer Componentで行い、インタラクティブな部分をClient Componentに分離するパターンがBulletproof Reactの原則と相性の良い設計です。

ESLintによるimport制約の実装

feature-basedアーキテクチャの依存ルールをESLintで自動的に検証する方法です。

eslint-plugin-import-accessの活用

feature間の不正なインポートを検知するESLintルールを設定できます。

// .eslintrc.cjs
module.exports = {
  rules: {
    'no-restricted-imports': [
      'error',
      {
        patterns: [
          {
            group: ['@/features/*/*'],
            message:
              'featureの内部モジュールを直接インポートしないでください。公開APIのindex.tsを経由してください。',
          },
        ],
      },
    ],
    'import/no-cycle': 'error',
  },
};

公開APIの定義

各featureのindex.tsでエクスポートするモジュールを制限し、feature内部の実装詳細を隠蔽します。

// src/features/discussions/index.ts
export { DiscussionList } from './components/discussion-list';
export { useDiscussions } from './api/use-discussions';
export type { Discussion } from './types';

導入ステップ:既存プロジェクトへの段階的適用

既存のReactプロジェクトにBulletproof Reactの設計を一度に導入するのは現実的ではありません。段階的なアプローチを推奨します。

Phase 1: コード品質基盤の整備(1〜2週間)

  1. ESLint + Prettierの設定を見直し、チームのルールを統一する
  2. tsconfig.jsonにパスエイリアス(@/*)を追加する
  3. Huskyを導入してコミット前チェックを自動化する

Phase 2: ディレクトリ構成の移行(2〜4週間)

  1. src/features/ディレクトリを作成する
  2. 新規機能からfeature-based構成で開発する
  3. 既存コードは改修のタイミングで段階的に移行する

Phase 3: API層と状態管理の整理(2〜4週間)

  1. TanStack Queryを導入し、サーバー状態の管理を分離する
  2. API Clientをsrc/lib/api-client.tsに一元化する
  3. 各featureのapi/にリクエスト関数とカスタムフックを移動する

Phase 4: テスト・エラーハンドリングの強化(継続的)

  1. MSWを導入してAPIモックを共通化する
  2. Error Boundaryをfeature単位で配置する
  3. Vitestでの統合テストを順次追加する

パフォーマンス最適化のポイント

Bulletproof Reactのパフォーマンスドキュメントでは、以下の最適化手法が推奨されています。

コード分割

React.lazyと動的インポートを使い、ルート単位でコードを分割します。初期ロードで必要なコードだけをバンドルに含め、残りはユーザーがアクセスした時点で読み込みます。

再レンダリングの抑制

  • 状態をできるだけ使用箇所の近くに配置する(コロケーション原則)
  • React.memoは測定に基づいてのみ適用する
  • 子コンポーネントのパターンで不要な再レンダリングを回避する

画像最適化とWeb Vitals

画像の遅延読み込み、適切なフォーマット選択(WebP / AVIF)、Core Web Vitals(LCP、FID、CLS)の計測と改善を継続的に行います。

まとめ

Bulletproof Reactは、Reactプロジェクトの設計に悩むチームにとって、再現性のある構造パターンを提供するリファレンスです。feature-basedディレクトリ構成、状態の5分類、API層の3層構造、統合テスト重視のテスト戦略など、各領域で具体的な実装指針が示されています。

既存プロジェクトへの導入はPhase分けで段階的に進められるため、「全面的なリライト」は不要です。まずはESLint設定とパスエイリアスの導入から始め、新規機能をfeature-basedで構築していくアプローチが実践的です。

公式リポジトリ(alan2207/bulletproof-react)にはサンプルアプリケーションのソースコードとドキュメント全文が公開されているため、チーム内での設計議論の出発点として活用できます。