React Lazy Loading を実務で使いこなす|パフォーマンス改善の実装パターン
1. React Lazy Loading の基礎知識
React lazy loading とは、必要なタイミングでコンポーネントを動的に読み込む技術です。初期ページロード時には全てのコンポーネントを読み込むのではなく、ユーザーが実際にアクセスするタイミングで読み込むことで、バンドルサイズを削減し、初期ロード時間を短縮できます。
実務では「ページの下部に配置されたモーダル」「管理画面の高度な分析ツール」「条件付きで表示される複雑なコンポーネント」など、すぐに必要でないコンポーネントをlazy loadingの対象にします。
React.lazy と Suspense の役割
React.lazy はコンポーネントを動的インポートでラップし、Suspense はそのロード中の状態を管理します。Suspense の fallback プロパティには、読み込み中に表示するコンポーネント(ローディングスピナーなど)を指定します。
2. 業務でのユースケース
実務で lazy loading が活躍するシーンは多岐にわたります。
ユースケース1:ダッシュボードアプリケーション
営業管理ツールやアナリティクスダッシュボードでは、複数のグラフやテーブルが存在します。全てのコンポーネントを初期ロードすると、バンドルサイズが数MB に膨れ上がります。タブごと、セクションごとに lazy loading を適用することで、必要な部分だけを先読みできます。
ユースケース2:マルチテナントシステム
テナントによって利用可能な機能が異なるシステムでは、ユーザーに不要な機能のコンポーネントを読み込まないことで、パフォーマンスと秘匿性が両立します。
ユースケース3:条件付きフローの複雑な業務ロジック
ユーザーの選択によって異なる処理フローを辿るアプリケーション(例:注文管理システムで「返品手続き」「交換手続き」など)では、各フローのコンポーネントを lazy loading で管理します。
3. 実装コード
基本的な使い方
import React, { Suspense, lazy } from 'react';
// lazy でコンポーネントをインポート
const AdvancedAnalytics = lazy(() => import('./AdvancedAnalytics'));
const UserManagement = lazy(() => import('./UserManagement'));
const ReportGenerator = lazy(() => import('./ReportGenerator'));
// ローディング表示用コンポーネント
const LoadingSpinner = () => (
読み込み中...
);
export function Dashboard() {
const [activeTab, setActiveTab] = React.useState('analytics');
return (
}>
{activeTab === 'analytics' && }
{activeTab === 'users' && }
{activeTab === 'report' && }
);
}
実務例:営業管理ダッシュボード
以下は、実際のプロジェクトで使用しているコードパターンです。営業チームが使用するダッシュボードで、重たい分析機能を lazy loading で読み込みます。
import React, { Suspense, lazy, useState, useCallback } from 'react';
import { ErrorBoundary } from 'react-error-boundary';
// 各機能のコンポーネントを遅延ロード
const SalesForecasting = lazy(() =>
import('./features/SalesForecasting').then(m => ({ default: m.SalesForecasting }))
);
const CustomerSegmentation = lazy(() =>
import('./features/CustomerSegmentation').then(m => ({ default: m.CustomerSegmentation }))
);
const RevenueTrend = lazy(() =>
import('./features/RevenueTrend').then(m => ({ default: m.RevenueTrend }))
);
const CompetitorAnalysis = lazy(() =>
import('./features/CompetitorAnalysis').then(m => ({ default: m.CompetitorAnalysis }))
);
interface TabType {
id: string;
label: string;
component: React.LazyExoticComponent<() => JSX.Element>;
}
const tabs: TabType[] = [
{ id: 'forecast', label: '売上予測', component: SalesForecasting },
{ id: 'segment', label: '顧客セグメント', component: CustomerSegmentation },
{ id: 'trend', label: '収益トレンド', component: RevenueTrend },
{ id: 'competitor', label: '競合分析', component: CompetitorAnalysis },
];
const LoadingFallback = () => (
データを読み込んでいます...
);
const ErrorFallback = ({ error, resetErrorBoundary }: any) => (
エラーが発生しました: {error.message}
);
export function SalesDashboard() {
const [activeTabId, setActiveTabId] = useState('forecast');
const [loadedTabs, setLoadedTabs] = useState>(new Set(['forecast']));
// タブ切り替え時に該当コンポーネントを事前読み込み
const handleTabChange = useCallback((tabId: string) => {
setActiveTabId(tabId);
setLoadedTabs(prev => new Set([...prev, tabId]));
}, []);
// 次のタブをプリフェッチ(オプション)
const handleMouseEnter = useCallback((tabId: string) => {
setLoadedTabs(prev => new Set([...prev, tabId]));
}, []);
const activeTab = tabs.find(tab => tab.id === activeTabId);
const ActiveComponent = activeTab?.component;
return (
営業管理ダッシュボード
}>
{ActiveComponent && }
);
}
ルートベースの lazy loading
React Router を使用する場合、ページ単位での lazy loading が一般的です。
import React, { lazy, Suspense } from 'react';
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';
const HomePage = lazy(() => import('./pages/HomePage'));
const AdminPanel = lazy(() => import('./pages/AdminPanel'));
const UserProfile = lazy(() => import('./pages/UserProfile'));
const Settings = lazy(() => import('./pages/Settings'));
const PageLoader = () => (
ページを読み込んでいます...
);
export function AppRouter() {
return (
}>
} />
} />
} />
} />
);
}
4. よくある応用パターン
パターン1:プリフェッチング
ユーザーがマウスをホバーした時点で、次のコンポーネントを読み込み開始します。これにより、実際のクリック時にはキャッシュから素早く表示できます。
import { useEffect, useState } from 'react';
function useLazyPrefetch(importFunc: () => Promise) {
const [isPrefetched, setIsPrefetched] = useState(false);
const prefetch = () => {
if (!isPrefetched) {
importFunc();
setIsPrefetched(true);
}
};
return { prefetch };
}
// 使用例
const { prefetch: prefetchAnalytics } = useLazyPrefetch(
() => import('./AdvancedAnalytics')
);
パターン2:条件付き lazy loading
ユーザーの権限レベルに応じてコンポーネントを読み込み分けます。
import React, { Suspense, lazy } from 'react';
const AdminTools = lazy(() => import('./AdminTools'));
const AnalystTools = lazy(() => import('./AnalystTools'));
const ViewerTools = lazy(() => import('./ViewerTools'));
interface UserRole {
role: 'admin' | 'analyst' | 'viewer';
}
export function FeaturePanel({ role }: UserRole) {
const getComponentByRole = () => {
switch (role) {
case 'admin':
return AdminTools;
case 'analyst':
return AnalystTools;
default:
return ViewerTools;
}
};
const Component = getComponentByRole();
return (
読み込み中...

