Expo/React Nativeアプリのサイズ削減手順2025|Hermes・R8・アセット最適化で初回DL容量を削るチェックリスト
結論:Hermes → R8 → アセット → 依存の順に、毎段階で実測する
Expoアプリの容量削減で失敗する原因はほぼ一つに絞られる。思いつきの順で手を付けて、どれが効いたか分からなくなることだ。
効く順番は決まっている。
- 削減前のベースラインを実測する
- Hermes とネイティブ配布形式を確認する
- Android の R8 / ProGuard とリソース圧縮を入れる
- 画像・フォント・Lottie を最適化する
- 不要ライブラリを棚卸しする
各段階のあとに必ずビルドして数値を記録する。これをやらないと「削ったつもり」で終わる。
まず削減前の実測値を取る
比較すべきは「ビルド成果物のサイズ」ではなく「ユーザーの初回ダウンロードサイズ」だ。AABのファイルサイズと、Play配信時に端末が実際に落とすサイズは別物になる。
Androidは bundletool でダウンロードサイズを直接見られる。
bundletool build-apks --bundle=app.aab --output=app.apks
bundletool get-size total --apks=app.apks
iOSは Xcode Organizer からエクスポートしたときの App Thinning Size Report、または App Store Connect のアプリサイズ表示を使う。
JSバンドル側の内訳はこう見る。
npx expo export --platform android
du -sh dist
パッケージ単位の内訳が欲しいときは source-map-explorer や react-native-bundle-visualizer を併用する。エクスポート系のオプション名はSDKバージョンで変わるため、実行前に公式ドキュメントの最新情報を確認してほしい。
段階1:Hermes とネイティブ配布の確認
近年の Expo SDK では Hermes が既定エンジンだが、古いプロジェクトから移行したアプリではJSCのまま残っていることがある。
{ "expo": { "jsEngine": "hermes" } }
HermesではJSがバイトコード(.hbc)へ事前変換され、バンドル相当分のサイズと起動時間が下がる。ただしスタックトレースの扱いが変わるので、Sentry等へのソースマップアップロード運用を先に整えてから切り替えたほうが安全だ。
配布形式も合わせて確認する。PlayへAABを出していればABIごとの分割配信は自動で行われる。APKを直接配布している運用だけが、全ABI入りで膨らむ。
バンドルとネイティブ層の関係を体系的に押さえたい場合は

サンプルコードで作りながら学ぶReact Native実践入門 (技術の泉シリーズ(NextPublishing))
参考価格¥2,200(税込)
内部構造から理解したいときの一冊
Amazonで最新価格をチェックのような書籍を1冊通すと、この後の判断が速くなる。
段階2:R8 / ProGuard とリソース圧縮(Android)
Managed workflowなら expo-build-properties で設定する。
["expo-build-properties", {
"android": {
"enableProguardInReleaseBuilds": true,
"enableShrinkResourcesInReleaseBuilds": true
}
}]
ここが「効くが最も壊れやすい」段階だ。リフレクションを使うSDKのクラスが削られると、リリースビルドだけ実行時に落ちる。keepルールを足し、リリース構成での実機QAを必ず通す。
-keep class com.example.model.** { *; }
-keepattributes Signature,*Annotation*
shrinkResourcesも同様で、文字列から動的に参照しているリソースは消える可能性がある。ここだけは「入れて終わり」にせず、主要導線を手で触って確認する。
段階3:アセット最適化
意外に大きいのはJSではなくアセットであることが多い。
| 対象 | よくある状態 | 対処 |
|---|---|---|
| PNG画像 | 3x想定の大サイズをそのまま同梱 | WebP化・実表示サイズへリサイズ |
| 日本語フォント | フルセット同梱で数MB | pyftsubsetでサブセット化、または端末標準フォント |
| Lottie | 高解像度ラスタ画像を内包したJSON | ベクター化・不要レイヤー削除 |
| 動画・音声 | 初回起動に不要なものまで同梱 | リモート配信+キャッシュ |
WebPはAndroidが標準対応、iOSも比較的新しいOSでは扱えるが、対応範囲は実機で確認してから全面移行する。
起動直後に必要ないアセットを同梱せず配信へ逃がすのが、単発では一番効く。ストレージ側の構成はCloudflare R2で画像アップロードを最安構成で実装する手順が参考になる。
段階4:不要ライブラリの棚卸し
npx depcheck
npx expo-doctor
削減余地が出やすいのは次の4つだ。
- moment を使い続けている(dayjsやIntlへ置換)
- lodash を名前空間ごとimportしている(個別importへ)
- react-native-vector-icons で全アイコンフォントを同梱している(使うセットだけ登録)
- 検証用に入れたまま残った expo-* モジュールやUIライブラリ
依存を1つ抜くたびにビルドして計測する。まとめて抜くと、壊れたときの切り分けができない。
削減前後の記録テンプレート
段階ごとに1行ずつ埋める。数値がない改善は続かない。
| 段階 | 施策 | Android DL | iOS DL | 起動時間 | 実機QA |
|---|---|---|---|---|---|
| 0 | ベースライン | 38.4MB | 62.1MB | 2.4s | - |
| 1 | Hermes + AAB配信 | 29.1MB | 55.8MB | 1.9s | OK |
| 2 | R8 + shrinkResources | 26.3MB | 55.8MB | 1.9s | 要確認 |
| 3 | アセット最適化 | 21.7MB | 47.0MB | 1.8s | OK |
| 4 | 依存棚卸し | 18.9MB | 42.5MB | 1.7s | OK |
この数値は画面数40・依存60パッケージ規模のアプリでの一例にすぎない。構成によって効き方は大きく変わるので、絶対値ではなく「自分のアプリでどの段階が何MB効いたか」を残すことに意味がある。
注意点とトレードオフ
正直に書いておく。
- R8は事故が起きる段階:難読化でリフレクション依存のコードが壊れる。削減幅の割にQAコストが高い
- Hermes移行はデバッグ体験が変わる:ソースマップ運用が未整備だと本番調査が苦しくなる
- アセット圧縮は品質劣化と背中合わせ:デザイナーと画質基準を合わせてから進める
- 容量削減の効果は劇的ではない:一般に容量が小さいほどインストール完了率は上がるとされるが、数値効果はアプリと市場によって差が大きい。過度な期待はしない
ビルド設定のオプション名やSDKの既定値は更新が早い。着手前に Expo と Android Gradle Plugin の公式ドキュメントで最新仕様を確認してほしい。
実行チェックリスト
- 削減前のAndroid DLサイズ・iOS DLサイズを記録した
- jsEngine が hermes になっている
- AAB(またはABI分割)で配布している
- R8 と shrinkResources を有効化し、keepルールを追加した
- リリース構成で主要導線の実機QAを通した
- 画像をWebP化し、実表示サイズへリサイズした
- フォントをサブセット化した
- depcheck / expo-doctor で不要依存を除去した
- 各段階の実測値を表に残した