Suspense × lazyで始めるコード分割:Reactアプリの初歩的最適化

Reactで開発を行っていると、アプリケーションの成長とともに、初期表示のパフォーマンスやユーザー体験に関する課題が表面化してきます。
そのような課題の一つに「初期読み込みの遅さ」があります。
Reactでは、この問題を解決するための仕組みとして、React.lazy
と Suspense
が提供されています。
本記事では、これらを用いたコード分割の基本的な考え方と、実装方法について、初心者の方でも理解できるように、豊富なサンプルコードとともに解説いたします。
はじめに:コード分割とは?
コード分割(Code Splitting)とは、アプリケーション全体のJavaScriptコードを機能ごとに小さなチャンクに分けて、必要になったときだけ読み込む技術です。
メリット:
- 初期読み込みサイズが小さくなり、表示が速くなる
- ユーザーが実際に利用する機能だけを読み込めるため、無駄がない
Reactでは、この機能を実現するための標準的な手法として React.lazy
と Suspense
が提供されています。
参考:
基本的な使い方
1. React.lazy
の基本構文
tsxconst LazyComponent = React.lazy(() => import('./MyComponent'));
このように書くことで、MyComponent
は必要になるまで読み込まれない、遅延読み込みコンポーネントになります。
2. Suspense
を使ったフォールバック表示
React.lazy
で読み込んだコンポーネントは、読み込みが完了するまでに時間がかかるため、その間に表示する**フォールバック(代替表示)**を指定する必要があります。
tsximport React, { Suspense } from 'react';
const LazyComponent = React.lazy(() => import('./MyComponent'));
export default function App() {
return (
<Suspense fallback={<div>読み込み中...</div>}>
<LazyComponent />
</Suspense>
);
}
このように、<Suspense>
でラップすることで、読み込み中に "読み込み中..."
が表示され、コンポーネントが読み込まれた後に LazyComponent
が描画されます。
よくある実装パターン
パターン1:ページごとに分割する
Next.jsでは自動でコード分割されますが、SPAの場合は手動で以下のように実装します。
tsxconst HomePage = React.lazy(() => import('./pages/Home'));
const AboutPage = React.lazy(() => import('./pages/About'));
function Router({ path }: { path: string }) {
return (
<Suspense fallback={<div>ページを読み込み中です...</div>}>
{path === '/' && <HomePage />}
{path === '/about' && <AboutPage />}
</Suspense>
);
}
パターン2:モーダルやダイアログなど、使用頻度の低いUIパーツを遅延読み込み
tsxconst LazyModal = React.lazy(() => import('./components/Modal'));
function App() {
const [isOpen, setIsOpen] = React.useState(false);
return (
<div>
<button onClick={() => setIsOpen(true)}>モーダルを開く</button>
{isOpen && (
<Suspense fallback={<div>モーダルを読み込み中...</div>}>
<LazyModal onClose={() => setIsOpen(false)} />
</Suspense>
)}
</div>
);
}
このようにすることで、実際に開かれるときに初めてモーダルを読み込むようになります。
注意点とベストプラクティス
1. Suspenseは単一コンポーネントに対して複数設置可能
必要な箇所ごとに <Suspense>
を使うことで、読み込みの粒度を細かく制御できます。
tsx<Suspense fallback={<Loader1 />}>
<Component1 />
</Suspense>
<Suspense fallback={<Loader2 />}>
<Component2 />
</Suspense>
2. エラーハンドリングには ErrorBoundary
を併用
React.lazy
で読み込んだコンポーネントが失敗する場合のために、ErrorBoundary
を使って保険をかけておくのがベストです。
公式参考:
tsxclass ErrorBoundary extends React.Component<any, { hasError: boolean }> {
constructor(props: any) {
super(props);
this.state = { hasError: false };
}
static getDerivedStateFromError() {
return { hasError: true };
}
render() {
if (this.state.hasError) {
return <h1>エラーが発生しました。</h1>;
}
return this.props.children;
}
}
function App() {
return (
<ErrorBoundary>
<Suspense fallback={<div>読み込み中...</div>}>
<LazyComponent />
</Suspense>
</ErrorBoundary>
);
}
高度な活用:React Routerとの併用
React Router v6
以降では、lazy()
を使ってルートごとに遅延読み込みが可能です。
参考:
tsximport {
createBrowserRouter,
RouterProvider,
Route,
} from 'react-router-dom';
import { Suspense, lazy } from 'react';
const Home = lazy(() => import('./routes/Home'));
const About = lazy(() => import('./routes/About'));
const router = createBrowserRouter([
{
path: '/',
element: (
<Suspense fallback={<div>読み込み中...</div>}>
<Home />
</Suspense>
),
},
{
path: '/about',
element: (
<Suspense fallback={<div>読み込み中...</div>}>
<About />
</Suspense>
),
},
]);
function App() {
return <RouterProvider router={router} />;
}
まとめ
React.lazy
とSuspense
を活用することで、Reactアプリの初期表示速度を改善できます。- フォールバックやエラー処理と組み合わせることで、より柔軟かつ安定したUXを実現できます。
- ページ単位の分割、モーダルなどのオンデマンドUIへの適用など、活用範囲は広いです。
React Router
などルーティングライブラリとも自然に統合できます。
今後は React Server Components
などとの併用も視野に入れることで、さらに効率的な開発が可能になります。
コード分割はパフォーマンス改善の第一歩です。ぜひ実際のプロジェクトに取り入れて、Reactのパフォーマンスを最大限に引き出してください。