積み上げログ技術ブログ記事一覧へ

自主執筆記事

5207件のJSONをそのままimportしたら本番バンドルが4.3MBになった話

Reactの検索UIで`import data from './data.json'`していたら、データが5207件に増えた瞬間にメインバンドルが4.3MBになった。原因と、63KBまで削った方法の記録。

パフォーマンス改善公開: 2026/08/08更新: 2026/08/08
ReactViteパフォーマンスSEO

01 / 本文

結論

JSONデータをimport data from './data.json'で読み込むと、ビルド時にJSがそのまま抱え込んでバンドルに焼き込まれる。件数が少ないうちは気づかないが、データが数千件規模に増えた瞬間に致命的なサイズになる。

対処は単純で、データをpublic/配下に置いてfetch()で実行時に取得する形に変えるだけ。今回は4.3MB(gzip後324KB)のメインバンドルが199KB(gzip後63KB)まで縮んだ。

02 / 本文

背景と課題

検索対応の一覧ページを持つサイトで、当初はデータが282件だった。src/data/actresses.jsonをstatic importしてuseMemoでクライアント側フィルタする、よくある実装だった。

そのデータをAPI経由で実データに置き換える作業をしたところ、件数が282件から5207件に増えた。ビルドすると警告が出た。「Some chunks are larger than 500 kB after minification」。実際に見てみるとメインのJSファイルが4,301.94 kB、gzip後でも324.31 kBあった。

この状態だと、トップページを開いた瞬間に4.3MBのJSをダウンロード・パースしないと検索欄すら動かない。モバイル回線なら数秒単位で無駄になる。

03 / 本文

試したことと結果

まずやったのは原因の特定。バンドル内訳を見るまでもなく、importしているJSONのファイルサイズを確認したら約4.9MBだった。これがほぼそのままJSにインライン化されていた。

修正方針は「ビルド時に静的解決させない」こと。具体的には次の3つを行った。

1. データファイルをsrc/data/からpublic/data/に移動する。Viteはpublic/配下をそのまま静的アセットとしてコピーするだけで、JSにバンドルしない。

2. コンポーネント側のimport data from './data.json'を削除し、useEffect内でfetch('/data/actresses.json')してuseStateに格納する形に変更した。

3. useMemoの依存配列に取得したデータの state を追加し忘れていたので、そこも直した。ここを直し忘れると、非同期取得が完了してもクエリを変えるまで画面が更新されない、という別のバグを踏む。

副次的な効果として、検索結果を素で全件(5207件)DOM に描画していた部分も上限60件に制限した。バンドルサイズとは別軸だが、初期表示のブラウザ負荷にも効く変更だったので合わせて実施した。

ビルドし直した結果、メインJSは199.15 kB(gzip 63.08 kB)。約95%の削減。

04 / 本文

振り返り

「JSONをimportする」という書き方は、プロトタイプ段階やデータ件数が少ないうちは何の問題もない。問題は、データが後から成長する前提を最初のコード設計に織り込んでいなかったこと。

教訓として、外部データを扱うコンポーネントを書くときは「このデータは将来何件まで増えうるか」を先に考え、増える可能性があるなら最初からfetchベースにしておく方が手戻りが少ない。

次回への改善点は、ビルド時のバンドルサイズをCIで閾値チェックする仕組みを入れること。今回はvite buildの警告で気づけたが、警告を読み飛ばしていたら本番で気づくのがもっと遅れていた。

コメント

この記事へのコメント

お名前だけで投稿できます。アカウント登録やログインは不要です。 いただいたコメントは確認のうえ公開します。