【React】ビルド最適化:バンドルサイズを小さくする方法

JavaScript

こんにちは、かつコーチです。

前回は開発・本番で値を切り替える環境変数の管理方法を解説しました。

デプロイ・環境変数と整えてくると、次に気になってくるのが「アプリの表示が遅い」という体感速度の問題です。

今回は、その原因になりやすいバンドルサイズ(ビルド後にブラウザへ配信されるJavaScriptファイルの合計サイズ)を小さくする方法を解説します。

バンドルサイズとは?なぜ小さくする必要があるのか

バンドルとバンドルサイズの定義

バンドルとは、複数のJavaScriptファイルをビルド時に1つ(または複数)のファイルへまとめたものを指します。

ReactアプリはViteやwebpackといったツールで、開発中に書いた大量のファイルを、ブラウザが効率よく読み込める形にまとめてから配信します。

このまとめられたファイルの合計サイズがバンドルサイズで、値が大きいほどブラウザがダウンロード・解析・実行するのに時間がかかります。

バンドルサイズが与える影響

バンドルサイズが大きいと、とくにスマートフォンや回線速度が遅い環境で、画面が表示されるまでの時間が長くなります。

「表示されるまで3秒以上かかると離脱率が大きく上がる」というデータもあり、パフォーマンスはユーザー体験だけでなくビジネス指標にも直結します。

私が担当したプロジェクトでも、バンドルサイズを見直しただけで初回表示までの時間が半分近くまで短縮できた経験があります。

バンドルサイズを可視化する

rollup-plugin-visualizerの導入

何が容量を圧迫しているのか分からないまま最適化するのは非効率なので、まずは可視化から始めます。

npm install -D rollup-plugin-visualizer
// vite.config.ts
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import { visualizer } from "rollup-plugin-visualizer";

export default defineConfig({
  plugins: [
    react(),
    visualizer({
      open: true,
      filename: "dist/stats.html",
    }),
  ],
});

npm run buildを実行すると、dist/stats.htmlが生成され、どのライブラリがどれくらいの容量を占めているかを、面積の大きさで視覚的に確認できます。

「思っていたより日付ライブラリが大きい」「使っていないアイコンセットが丸ごと入っている」といった発見が、この時点でよく見つかります。

基本の最適化テクニック

動的インポートによるコード分割

すべてのページのコードを1つのファイルにまとめてしまうと、トップページを開いただけで管理画面のコードまでダウンロードすることになります。

コード分割(Code Splitting)を使い、ページ単位で必要になったときだけ読み込むようにしましょう。

import { lazy, Suspense } from "react";
import { Routes, Route } from "react-router-dom";

// 管理画面はログインした管理者しか使わないので、必要になるまで読み込まない
const AdminDashboard = lazy(() => import("./pages/AdminDashboard"));

function App() {
  return (
    <Suspense fallback={<p>読み込み中...</p>}>
      <Routes>
        <Route path="/admin" element={<AdminDashboard />} />
      </Routes>
    </Suspense>
  );
}

lazyと動的import()を組み合わせることで、/adminにアクセスしたタイミングで初めてそのコードがダウンロードされるようになります。

大きなライブラリを軽量な代替に置き換える

日付操作ライブラリのmoment.jsのように、多機能な分バンドルサイズが大きくなりがちなライブラリもあります。

こうしたライブラリは、必要な機能だけを持つ軽量な代替ライブラリに置き換えることで、サイズを大きく削減できることがあります。

  • 日付操作:momentdate-fnsdayjs
  • アイコン:アイコンセット全体のインポート → 使うアイコンだけ個別インポート

とくにアイコンライブラリは、書き方1つでバンドルサイズが数百KB単位で変わることがあるので、次の項目で詳しく見ていきます。

つまずきやすいポイント:ライブラリの丸ごとインポート

かつコーチが実際にハマった「アイコン1つで数百KB増加」事件

私がある案件で、アイコンライブラリを導入した直後にバンドルサイズが急増し、原因調査に半日費やしたことがあります。

原因は、たった1つのアイコンを使うためだけに、ライブラリ全体をインポートしてしまっていたことでした。

❌ Before:ライブラリ全体を丸ごとインポートしてしまう

import * as Icons from "react-icons/fa";

function SaveButton() {
  return (
    <button>
      <Icons.FaSave /> 保存する
    </button>
  );
}

import * as Iconsという書き方は、「このライブラリが持つすべてのアイコンをまとめて読み込む」という意味になります。

たった1つの保存アイコンを表示したいだけなのに、使っていない数百個のアイコンまでバンドルに含まれてしまい、視覚化ツールで確認したところ、このimport文だけでバンドルサイズが300KB以上増えていました。

✅ After:使うアイコンだけを個別にインポートする

import { FaSave } from "react-icons/fa";

function SaveButton() {
  return (
    <button>
      <FaSave /> 保存する
    </button>
  );
}

必要なアイコンだけを名前付きインポートすることで、ビルドツールのTree Shaking(使われていないコードを自動的に除外する仕組み)が正しく働き、余分なコードがバンドルに含まれなくなります。

「ライブラリ全体をimport * asで読み込んでいないか」は、バンドルサイズが気になったときに真っ先に疑うポイントとして覚えておくと役立ちます。

応用:画像・フォントの最適化

画像フォーマットの見直し

JavaScriptだけでなく、画像もページの表示速度に大きく影響します。

PNG・JPEGで書き出していた画像を、より軽量なWebPAVIF形式に変換するだけで、画質をほぼ保ったままファイルサイズを大幅に削減できることが多いです。

function HeroImage() {
  return (
    <picture>
      <source srcSet="/hero.avif" type="image/avif" />
      <source srcSet="/hero.webp" type="image/webp" />
      <img src="/hero.jpg" alt="トップページのメインビジュアル" loading="lazy" />
    </picture>
  );
}

<picture>タグで複数フォーマットを指定しておくと、対応しているブラウザから優先的に軽量なフォーマットを選んでくれます。

loading="lazy"を付け加えることで、画面外の画像は表示直前まで読み込みを遅らせることもできます。

ビルド後のサイズを継続的に監視する

一度最適化しても、新しいライブラリを追加するたびにバンドルサイズは再び膨らみがちです。

CIにsize-limitのようなツールを組み込み、「バンドルサイズが一定値を超えたらビルドを失敗させる」仕組みを作っておくと、気づかないうちに肥大化するのを防げます。

まとめ

この記事のポイント

  • バンドルサイズが大きいほど初回表示までの時間が伸び、離脱率にも影響する
  • rollup-plugin-visualizerなどでまず「何が容量を占めているか」を可視化する
  • ページ単位の動的インポートでコード分割し、不要な読み込みを減らす
  • import * asによるライブラリの丸ごとインポートは、バンドル肥大化の典型的な原因
  • 画像フォーマットの見直しやsize-limitでの継続監視も効果的

次に読むべき記事

デプロイ・パフォーマンスと整えたところで、次は日々の開発でよく出会うエラーとその解決法をまとめて紹介します。

→ 次の記事:Reactでよく出るエラーと解決法まとめ

タイトルとURLをコピーしました