肥大化したJavaScriptを軽くする改善手順
「機能を追加するたびにページが重くなっている気がする」「初回表示だけやたら白い画面が長い」——サービスが2〜3年運用されてくると、こうした声がよく上がります。原因を辿ると、たいていJSバンドルが数MBまで膨れ上がっていて、ユーザーがページを開くたびにその大半をダウンロード・パースさせられている状態になっています。この記事では、なぜバンドルが肥大化するのか、どう計測して原因を特定するか、そしてどう軽くするかを具体的に解説します。
「バンドルが重い」とは何が起きている状態か
JSバンドルとは、ブラウザに配信する前にビルドツール(webpack・Vite・Next.jsのビルダーなど)がJavaScriptのソースコードとライブラリをひとつ(または数個)のファイルにまとめたものです。これが肥大化すると、次の3つが同時に悪化します。
- ダウンロード時間: ファイルサイズが大きいほど、特にモバイル回線で転送に時間がかかる
- パース・コンパイル時間: ブラウザがダウンロードしたJSを実行可能な状態に変換する処理も、コード量に比例して重くなる
- 実行時間: 使っていないコードも含めてすべて評価されるため、メインスレッドがブロックされる時間が伸びる
これらはLCP(Largest Contentful Paint)やTBT(Total Blocking Time)といったCore Web Vitalsの指標に直接跳ね返り、検索順位やユーザー離脱率に影響します。目安として、初期表示で読み込むJS(gzip後)が300KBを超えたあたりから体感速度の劣化が顕著になり、1MBを超えている場合はほぼ確実に改善余地があります。Next.jsを使ったサイトでページ速度そのものが問題になっているケースはNext.jsサイトのページ速度が遅い原因の調べ方でも扱っているので、レンダリング側の問題も疑わしい場合はあわせて確認してください。
まず計測する: bundle analyzerを入れる
原因を推測で語らず、実際に何が入っているかを可視化するところから始めます。
- webpack:
webpack-bundle-analyzer - Vite:
rollup-plugin-visualizer - Next.js:
@next/bundle-analyzer
これらはビルド後のバンドル内容をツリーマップで可視化し、「どのモジュールが何KB占めているか」を一目で見せてくれます。導入コストは低く、既存プロジェクトに追加してビルドを1回走らせるだけで済みます。この可視化結果が、以降のすべての改善判断の根拠になります。感覚で「このライブラリが重そう」と当たりをつけて着手すると、実際には別の箇所がボトルネックだったというケースが少なくありません。
肥大化の典型的な原因
計測結果を見ると、原因はだいたい次の5パターンのどれかに分類できます。
1. 重量級ライブラリの安易な採用
日付操作にmoment.js(ロケールデータ込みで200KB超になりがち)、アイコンにアイコンフォント一式、UIに機能過多なコンポーネントライブラリなど、「便利だから」で入れたライブラリが単体でバンドルの大半を占めているケースです。特にmoment.jsは公式もメンテナンスモードを表明しており、置き換え候補が明確な典型例です。
2. ライブラリの全体import
import _ from "lodash" のように、ライブラリ全体をインポートして一部の関数しか使っていないパターンです。ツリーシェイキング(未使用コードの自動削除)が効くライブラリでも、書き方次第で効かなくなります。名前空間インポート(import * as)や、ES Modules対応していない古いライブラリのCommonJS版を掴んでいる場合、ビルドツールは「どの関数が使われているか」を静的解析できず、全体を含めてしまいます。
3. 重複した依存関係
package.jsonの依存が積み重なる過程で、同じライブラリの異なるバージョンが複数バンドルに混入していることがあります。特にモノレポやマイクロフロントエンド構成、あるいは長期間メンテナンスされたプロジェクトでよく見られ、bundle analyzerのツリーマップで同名のライブラリが複数箇所に出現していたら、これが原因です。
4. 使われなくなった機能のコード
過去に作って今は使っていない機能、A/Bテストの片方の分岐、削除されたページの参照が残ったままのコンポーネントなど、「デッドコード」がそのままバンドルに含まれ続けているケースです。ツリーシェイキングは「到達不能」なコードしか除去できないため、どこかから1箇所でもimportされていれば残り続けます。
5. すべてを初期バンドルに含めている
管理画面・モーダル・特定ページでしか使わない重い機能(リッチテキストエディタ、グラフ描画、PDF生成など)を、遅延読み込みせずトップページと同じバンドルに含めてしまっているケースです。ユーザーの大半が使わない機能のために、全員が初期表示でその重さを負担している状態です。
具体的な改善策
原因が特定できたら、次の対策を優先度順に適用します。
Code Splitting(コード分割)
ルート単位・コンポーネント単位でバンドルを分割し、必要になったタイミングで読み込む方式です。React であれば React.lazy + Suspense、Next.js であれば next/dynamic を使い、初期表示に不要なコンポーネント(モーダル、タブの非アクティブなコンテンツ、管理画面限定の機能など)を分離します。これだけで初期バンドルが30〜50%削減できるケースも珍しくありません。
Dynamic Import で機能単位の遅延読込
「特定のボタンを押したときだけ必要なライブラリ」(PDF生成、画像編集、CSVパースなど)は、import()構文でその操作が発生した瞬間にロードする形に変えます。これにより、ほとんどのユーザーが一度も使わない重量級ライブラリを、初期表示の計算から完全に除外できます。
軽量ライブラリへの置換
- moment.js → date-fns や dayjs(前者はツリーシェイキング可能、後者は2KB程度)
- lodash全体import → 必要な関数のみを個別importする lodash-es、または不要ならネイティブJSで代替
- 独自実装で十分な小さな処理に、機能過多なライブラリを使っていないか確認する
置換は影響範囲の調査(そのライブラリのAPIをどこまで使っているか)が必要なため、機械的な作業ではなく設計判断を伴います。特にmoment.jsは日付フォーマットの挙動が微妙に異なることがあるため、置換後の表示崩れがないかの確認が欠かせません。
Tree Shaking が効く書き方への統一
importの書き方をESMベースの名前付きインポートに統一し、ビルド設定(webpackのsideEffectsフラグ、Babelの設定など)がツリーシェイキングを妨げていないか確認します。CSSやポリフィルを読み込むためだけのimportがsideEffectsとして誤って除外・混入されるケースもあるため、設定変更後は必ずビルド結果を確認します。
未使用コードの削除
デッドコード化した機能・ページ・分岐は、思い切って削除します。「念のため残す」判断がバンドル肥大化の温床になりやすいため、Gitで履歴を追える以上、使われていないと確認できたコードは消してよいという前提で棚卸しするのが現実的です。
効果の測り方
改善は「体感軽くなった気がする」で終わらせず、数値で確認します。
- バンドルサイズ(gzip後): 改善前後でbundle analyzerの出力を比較し、KB単位の削減量を記録する
- Lighthouse のパフォーマンススコアとCore Web Vitals: LCP・TBT・FID/INPの改善幅を本番相当の環境で計測する
- 実ユーザーデータ(RUM): 可能であればGoogle Analyticsやモニタリングツールの実測値で、改善が実際のユーザー体験に反映されているかを確認する
ビルド環境の数値だけでなく、実際のネットワーク条件(スロットリングをかけた状態)での計測も忘れずに行うと、社内での説明材料としても説得力が増します。
外注する場合の進め方
バンドル分析自体は数時間で終わりますが、原因の特定と対策の実装は「どのコードが本当に必要か」というプロダクト理解が伴うため、外部に依頼する場合はいきなり全面リファクタを頼むのではなく、まず計測とボトルネック特定のフェーズを独立して依頼するのが現実的です。原因が明確になれば、影響範囲の小さいものから段階的に着手でき、リリースのたびに効果を確認しながら進められます。
torcheees では既存プロダクト改善のサービスの中で、bundle analyzerによる計測から原因特定、code splitting・ライブラリ置換の実装まで一貫して支援しています。フロントエンド技術ではReact・Next.jsを中心とした改善実績を扱っており、既存のビルド構成を理解した上で、影響範囲を抑えながら段階的に軽量化を進めます。バンドル最適化に着手する前の全体的な優先順位づけについては既存プロダクトを改善する最初のチェックポイントもあわせてご覧ください。
まとめ
- JSバンドルの肥大化はダウンロード・パース・実行のすべての時間を悪化させ、Core Web Vitalsと直結する。まずbundle analyzerで「何が」「どれだけ」含まれているかを可視化するところから始める
- 原因は「重量級ライブラリ」「全体import」「重複依存」「デッドコード」「初期バンドルへの詰め込みすぎ」の5パターンに大別でき、原因ごとに code splitting・dynamic import・軽量ライブラリ置換・未使用コード削除で対処する
- 改善は体感でなく数値(バンドルサイズ・Lighthouse・実ユーザーデータ)で確認し、影響範囲の小さい対策から段階的に適用するのが安全
初期表示が重い、機能追加のたびにページが遅くなっている感覚がある場合、torcheees では「既存プロダクト改善 診断」として現状のバンドル構成を計測し、削減余地と概算費用をご提示します。まずはお問い合わせフォームからご相談ください。