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/Aはfeatures/Bを直接インポートしないcomponents/(共有層)はfeatures/を参照しないlib/はfeatures/を参照しない- feature内部のモジュールは
index.ts経由で公開APIを制御する
この制約により、各featureを独立して開発・テスト・削除できる構造が保たれます。
Atomic Designとの構造比較
Bulletproof Reactのfeature-basedアプローチと、従来のAtomic Designを比較すると、設計判断の基準が異なることがわかります。
| 観点 | Atomic Design | Bulletproof 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点です。
- コロケーション: コンポーネント・関数・スタイル・状態を、使用箇所のできるだけ近くに配置する
- 巨大コンポーネントの回避: ネストしたレンダリング関数を含む大きなコンポーネントは分割する
- コードの一貫性: PascalCase命名、Prettier、ESLintで規約を自動化する
- Props数の制限: Propsが増えすぎたらcompositionパターン(children / slots)で解決する
- 共有コンポーネントの抽象化: サードパーティUIコンポーネントはラップして使い、将来の差し替えに備える
コンポーネントライブラリの選択肢
| 種別 | ライブラリ | 特徴 |
|---|---|---|
| フル機能型 | Chakra UI | 開発体験重視、アクセシビリティ標準装備 |
| フル機能型 | MUI | Material Design実装、最大級のコンポーネント数 |
| フル機能型 | Mantine | モダンなAPI設計、豊富なhooks |
| フル機能型 | AntD | 管理画面向け、多機能だがスタイルの上書きに制約あり |
| ヘッドレス型 | Radix UI | スタイルなしのプリミティブ、アクセシビリティ標準準拠 |
| ヘッドレス型 | Headless UI | Tailwind CSS公式チームが開発 |
| ヘッドレス型 | react-aria | Adobe製、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週間)
- ESLint + Prettierの設定を見直し、チームのルールを統一する
tsconfig.jsonにパスエイリアス(@/*)を追加する- Huskyを導入してコミット前チェックを自動化する
Phase 2: ディレクトリ構成の移行(2〜4週間)
src/features/ディレクトリを作成する- 新規機能からfeature-based構成で開発する
- 既存コードは改修のタイミングで段階的に移行する
Phase 3: API層と状態管理の整理(2〜4週間)
- TanStack Queryを導入し、サーバー状態の管理を分離する
- API Clientを
src/lib/api-client.tsに一元化する - 各featureの
api/にリクエスト関数とカスタムフックを移動する
Phase 4: テスト・エラーハンドリングの強化(継続的)
- MSWを導入してAPIモックを共通化する
- Error Boundaryをfeature単位で配置する
- 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)にはサンプルアプリケーションのソースコードとドキュメント全文が公開されているため、チーム内での設計議論の出発点として活用できます。