Next.jsでReact Iconsを使うと開発サーバー(next dev)が重いので対策を入れた

株式会社リゾーム 業務ソリューション事業グループの岡部です。BOND WORKSのフロントエンドエンジニアをやっています。

BOND WORKSのフロントエンドはNext.jsで開発しており、アイコンライブラリとしてReact Icons(react-icons)を採用しています。

React IconsにはFont Awesome、Material Design Icons、Bootstrap Iconsなどをはじめとするメジャーなアイコンパックが多く含まれているため、個々のアイコンライブラリを別々にインストールする必要がありません。またReactコンポーネントとして提供されているため簡単に使えて便利です。

しかしNext.jsでReact Iconsを使用すると、開発サーバー(next dev)の実行時にパフォーマンスが著しく悪化する問題が発生しました。今回はこの問題について調査を行い、また暫定的ではありますが対策を入れたのでご紹介します。

TL;DR

  • Next.jsでReact Iconsを使うと next dev が重い
  • インポートの最適化を行う optimizePackageImports が効かず、アイコンパック内の全コンポーネントがバンドルされるのが原因
  • アイコンコンポーネントを @react-icons/all-files から個別にインポートすることで対策が可能

環境

  • Next.js 15.5.12 (App Router, Webpack)
  • React Icons 5.5.0

Next.jsとReact Iconsを組み合わせたときの問題点

前提知識:optimizePackageImports とは

Next.jsにはインポートを最適化する optimizePackageImports という機能があります。Vercelの解説記事によると、以下のようにバレルファイルからのインポートを検出し、モジュールを直接インポートする文に置換するそうです。コンパイル時に行われるので、バンドラにバレルファイルを解析させてTree shakingするよりも確実かつ低コストとのこと。

/* optimizePackageImports による最適化のイメージ */
// Before
import { module2 } from 'my-lib'
// After
import module2 from 'my-lib/module2'

React Iconsが最適化されない!

しかしReact Iconsでは optimizePackageImports が効かないみたいです(2026年2月現在)。公式ドキュメントには react-icons/* に対応していると書かれているのですが…🤔

下記Issueにあるように、アイコンを1つインポートしただけでアイコンパック全体がバンドルに含まれてしまいます。

最適化が効かない原因ですが、React Iconsは各アイコンコンポーネントを個別ファイルで提供していないため、import文を置換できないのだと思われます。

// これを最適化したいが…
import { IconName } from 'react-icons/md'
// ❌単体ファイルが存在しないのでこのように書けない!
import IconName from 'react-icons/md/IconName'

では、インポート元のファイルはどのような構造になっているのでしょうか?react-icons/md/index.mjs を見てみると、関数コンポーネントが直接定義・エクスポートされていることが分かります。1コンポーネント=1アイコンなので、Material Design Iconsパックに対応するこのファイルでは実に約4300個もの関数がエクスポートされ、ファイルサイズも2 MBを超えます。

// node_modules/react-icons/md/index.mjs より抜粋
export function Md123 (props) {
  return GenIcon({ /* 省略 */ })(props);
};
export function Md3dRotation (props) {
  return GenIcon({ /* 省略 */ })(props);
};
export function MdAbc (props) {
  return GenIcon({ /* 省略 */ })(props);
};
// ... (exportが4000個以上ある)

つまり、React Iconsにおけるアイコンパックごとのindexファイルは、厳密にはバレルファイルではないということですね。

開発体験の悪化

大量のexportを含むファイルは、ビルド時や実行時にパフォーマンスの問題を引き起こします。

本番環境ではビルド時の負荷こそ高いものの、Tree shakingが行われるため、実行時のパフォーマンスに問題はありませんでした。

しかし開発サーバー(next dev)ではTree shakingが行われないため、巨大なJavaScriptファイルをブラウザが読み込む必要があり、CPUに高い負荷がかかっていました。メモリの消費も激しかったです。特に複数のアイコンパックを参照している場合は顕著で、ページの初回表示に30秒~1分近くかかるなど、快適に開発できる状態ではありませんでした。

Issueに目立った動きがないのでしばらく修正は来なさそうですし、他のアイコンライブラリへの切り換えにも時間がかかります。そこで、Issue内のコメントを参考にして、@react-icons/all-files パッケージからアイコンを個別にインポートするという暫定対策を入れてみることにしました。

暫定対策:@react-icons/all-files からアイコンを個別にインポートする

React Iconsには、アイコンを個別のファイルに分けて提供する @react-icons/all-files パッケージが存在します。移行の際には以下のようにimport文を置き換える必要があります。

/* 置換例 */
// Before
import { MdMoreVert, MdPrint } from 'react-icons/md';

// After
import { MdMoreVert } from '@react-icons/all-files/md/MdMoreVert';
import { MdPrint } from '@react-icons/all-files/md/MdPrint';

1. @react-icons/all-files を導入する

肝心の @react-icons/all-files パッケージですが、npmの公式レジストリで公開されていません。下記Issueで言及されていますが、大量のアイコンを含む関係でファイル数が多くなりすぎて公開できないそうです。

そこで、当該Issue内のコメントにあるように、GitHubリポジトリのReleasesで配布されているものを使います。

# 5.5.0 を npm でインストールする例
npm install @react-icons/all-files@https://github.com/react-icons/react-icons/releases/download/v5.5.0/react-icons-all-files-5.5.0.tgz

2. import文を置換する

次にimportの置換を行います。

/* 置換例 (再掲) */
// Before
import { MdMoreVert, MdPrint } from 'react-icons/md';

// After
import { MdMoreVert } from '@react-icons/all-files/md/MdMoreVert';
import { MdPrint } from '@react-icons/all-files/md/MdPrint';

import文は以下のように複数行で記述される場合もあり、単純な文字列置換では対応が難しいです。

// 1行の場合
import { MdMoreVert, MdPrint } from 'react-icons/md';

// 複数行の場合
import {
  MdKeyboardArrowLeft,
  MdKeyboardArrowRight,
  MdKeyboardDoubleArrowLeft,
  MdKeyboardDoubleArrowRight,
} from 'react-icons/md';

そこで、AST(抽象構文木)を扱うことができる jscodeshift を使ってCodemodを作成することにしました。

Codemodの処理の流れは以下のようになります。

  1. react-icons/* からのimport文を探す

    例だと import { MdMoreVert, MdPrint } from 'react-icons/md' の部分

  2. import文からアイコンパック名を取得

    例だと 'react-icons/md'md の部分

  3. importされている識別子を取得

    例だと MdMoreVertMdPrint の2つ

  4. 2, 3で得た情報を元に個別のimport文を作成

    例だと以下の2つのimport文を表すオブジェクトを作成

    • import { MdMoreVert } from '@react-icons/all-files/md/MdMoreVert'
    • import { MdPrint } from '@react-icons/all-files/md/MdPrint'
  5. 実際のソースファイルを、元のimport文を置き換える形で書き換える

また、optimizePackageImports の問題が修正されたときに戻せるよう、逆の置換をするCodemodも作成しました。

改善を確認🚀

対策を入れた上で next dev を動かしてみると、改善前は表示に30秒以上かかっていたページが10~15秒ほどで表示されました!PCの負荷も体感できるレベルで軽減され、開発体験が向上しました🎉

念のためバンドルアナライザを使い、余計なファイルや巨大なIndexファイルの読み込みが無いことも確認しました。

おわりに

React Iconsはとても便利なライブラリですが、利用時は注意が必要です。

問題を調査し対策する中で、たくさんの学びがありました。

  • バレルファイルの問題点
  • Next.jsがどのようにインポートを最適化するか
  • jscodeshiftの使い方
  • などなど…

似たような状況で困っている方の参考になれば幸いです。