Expoでモバイル小売アプリをモダナイズする方法
Users • Development • React Native • February 6, 2026 • 21 minutes read
Jonathan Bones Guest Author
bitglowがExpo Prebuildを使ってDEPOTのReact Nativeアプリをどのようにモダナイズし、アップグレード時間を80%短縮し、パフォーマンススコアを36から90に向上させたかを解説します。この記事はbitglowのSenior Frontend Dev、Jonathan Bonesによるゲスト投稿です。彼はアップグレード、最適化、デバッグなどの詳細を大切にしています。You can find him on GitHub & Bluesky.
ExpoとReact Nativeは開発者に高速で美しいアプリを作る力を与えます。しかし、特に低スペック端末では細部に注意を払わないとその約束は簡単に薄れ、開発者のフラストレーションと顧客の不満につながります。心当たりはありますか?それがまさにDEPOT(ドイツの大手ホームデコレーション小売業者)が直面していた課題でした。
Expoの最新機能とExpoのCloud Services、そしてbitglowの深いReact Nativeの専門知識を組み合わせ、古くなったReact Nativeのセットアップを完全に刷新し、何年分もの技術的負債を取り除いて将来に耐えうるコードベースを構築しました。
なぜこれはあなたにとって重要なのか?技術的負債が開発チームを遅らせると、どんなに優れたアイデアでも顧客に届くのが遅くなります。証拠として、着手前のユーザーの声をどうぞ:
★☆☆☆☆
“The app could be so great if it didn’t keep crashing. You constantly have to wait because everything takes forever to load or doesn’t load at all.”
過去1年でアプリを段階的に改修するにつれて、フィードバックは変わり始めました。今では一部の懸念は特定の機能やバグに移っていますが、明らかにパフォーマンスに対する不満は減っています:
★★★★★
“Excellent app. It would be even better if users could bookmark a nearby store and the app would then show whether the item is available at that branch or not.”
この変化は一夜にして起きたわけではありません。既存コードベースの監査から始まり、痛点を理解してベースラインを確立することが最初の一歩でした。
コードベースの監査
参加時の監査で判明したのは次の構成でした:
- Ejected Expo app - React Native 0.63
- React Navigation v5(deprecatedなv4互換レイヤーを多用)
- Reduxによる状態管理 + SagasでAPI呼び出しなどの副作用を処理
- TypeScript v4.4
最新のセットアップとは言えませんでしたが、TypeScriptを活用することで大規模なリファクタリングを行っても新たな問題発生をほぼ抑えられる見通しがありました。
監査の結果、対応すべき主要な4つの領域が見えました:
- Prebuildを採用してReact Nativeのメンテナンス負荷を減らす
- アプリのパフォーマンス最適化
- ExpoのServicesでビルドとサブミッションを自動化する
- OTAデプロイで迅速なアップデートを行えるようにする
数週間から数日に:Expo Prebuildでアップグレードを加速
最大の課題は古い・未メンテナンスのReact Nativeバージョンの維持と更新でした。アプリはReact Native 0.63で稼働しており、最新安定版から数回分のメジャーリリースに遅れていました。Google PlayやApple App StoreのターゲットSDK更新ポリシーが頻繁になっているため、期限対応は負担が大きくなる一方です。
さらに、v4互換レイヤーに依存したReact Navigation v5の使用は、アップグレード時に微妙なバグを招く可能性を高めていました。過去にはReact Nativeの数回のアップグレードを手作業で行い、State of React Nativeの報告でもそれが痛点であると指摘されていました。Upgrade Helperの登場で作業は管理しやすくなりましたが、それでも数週間はかかっていました。
注: Expoは最近、アップグレードを支援する新しいClaude Code skillを導入しました。私たちはまだ試していませんが、非常に役立つと聞いています。
手作業での変更、ビルドのQA、バグ修正、リリース準備には数週間を要しました。これはフレームワークで必要なネイティブコードの管理に費やされている貴重な開発時間であり、スプリントでは機能開発を止めてアップグレードに注力するダウンタイムと見なされることが多かったです。
これを速く、安全に行う方法はないか?という問いに対して、expo prebuildの採用が有効でした。
Prebuildの採用
app.js conf 2022のContinuous Native Generationに関するキーノートは、プロジェクトでexpo prebuildを真剣に検討する決め手になりました。ios/とandroid/のネイティブプロジェクトを手動で管理する代わりに自動生成することで、アップグレードは短く予測可能になり、“機能のダウンタイム”が減ります。
DEPOT向けの推奨を検証するため、過去のプロジェクト経験や手作業にかけていた時間、ネイティブ/非ネイティブ依存関係の数、コードベース全体の規模などを元に見積もりを作成しました。その結果、マイグレーション自体は単一のReact Nativeアップグレードとほぼ同等の工数である一方、将来のアップグレードは従来の20%程度の時間で完了でき、数週間かかっていたアップグレードを数日に短縮できる、という結論に達しました。
私たちは複数のプロジェクトでReact Native CLIからExpoへのマイグレーションを完了しており、その詳細に精通しています。Expoチームとコミュニティの継続的な改善もあり、マイグレーションはますます予測可能で効率的になっています。
実務では、初期の工数は単一のRNアップグレードに相当することが多く、投資回収は以後のアップグレードで即座に始まります。DEPOTでは複数バージョンのRNアップグレードを並行して行ったため、その効果はさらに大きく、単一バージョンのアップグレードと同程度の工数で3バージョン分の移行が完了しました。
マイグレーションの準備
まずは依存関係の監査から始めました。package.jsonの各エントリをネイティブ依存か純粋JSかに分類し、unimportedを走らせて未使用ライブラリを特定しました。これにより最初のパスで@react-native-voice/voice、isomorphic-fetch、traverseを削除しました。
長年の技術的負債にも対処し、@react-navigation/compatレイヤーを最終的に削除して、StackActionsやNavigationActionsをuseNavigationフックにリファクタリングすることで、React Navigationの新バージョンへのアップグレードの道を開きました。
参照回数が少ない依存(≤3参照)はスキャンしていくつかは小さなカスタム実装で置き換えました。例: react-native-multi-tap-componentはバニラのRNコンポーネントに置き換えました。依存を刈り込むことでネイティブ統合リスクを大きく減らせ、Prebuild導入準備で最も効果的なステップとなりました。
ビルド設定のセットアップ
整理したpackage.jsonを用いて、残存するネイティブパッケージについてExpo config-pluginsリポジトリを参考にconfig-pluginサポートをチェックしました。多くのネイティブ依存は既にファーストパーティかコミュニティのプラグインがありました。プラグインが無いものはカスタムconfig pluginを実装する必要がありましたが、Expoのドキュメントと既存のプラグインを参照すれば数時間で対応できました。
マイグレーションは段階的に進めました。android/とios/フォルダを.gitignoreに追加し、アプリのエントリポイントを最小のレンダーに簡素化してまずネイティブ統合を復元し、続いてTypeScriptやビジネスロジックの問題に取り組めるようにしました:
export default function App ( ) {
return (
<View style={styles.container}>
<Text>If you can see this the app builds and starts</Text>
</View>
);
}
次に最新のExpo SDKをインストールし、expo-dev-clientを追加して、npx expo install expo --fix を実行してネイティブライブラリを更新し、インストールしたSDKバージョンに設定を合わせました。
app.jsonはapp.config.tsへ名前を変更し、APP_VARIANT環境変数に基づいてビルドフレーバーを切り替えるようにしました:
import { ExpoConfig } from "expo/config";
const name = {
development: "DEPOT (dev)",
preview: "DEPOT (preview)",
production: "DEPOT",
}[process.env.APP_VARIANT ?? "development"];
export default (): ExpoConfig => ({
name: name!,
version: "6.1.0",
});
iOSの初回ビルドは成功しました。🎉
AndroidビルドでいくつかのGradleエラーが発生しました。スタックトレースを掘ると、生成されたネイティブプロジェクトと非互換な古いEmarsys SDKが原因でした。対応は単純で、SDKをアップグレードしてビルドアーティファクトとキャッシュをクリーンにし、再ビルドするだけでした。
アプリ機能の復元
ネイティブ統合を整えた後、アプリの初期コードを戻したところ、ブロッキングな問題に遭遇しました: プロファイルタブへ遷移すると目に見えてトランジションがガクつき、その後クラッシュするというものです。スタックトレースと再現された挙動はアプリケーションロジックではなくナビゲーション層を示していました。
選択肢は2つ: メンテされていないv5にパッチを当てるか、負債に取り組んでサポートされている最新バージョン(v7)へアップグレードするか。長期的なメンテコストを鑑み、私たちはアップグレードの道を選びました(この作業自体も別の記事に値します)。
アップグレードのROI
未使用・もろい依存の削減、ナビゲーションとSDKのアップグレード、Prebuildによるネイティブプロジェクト自動生成の採用により、リスクが高く時間を取られていたワークフローを繰り返し可能で自動化されたプロセスに変換しました。メリットは明白です: TTU(Time‑To‑Upgrade)が速くなり、開発者のベロシティが著しく向上しました。ExpoとReact Nativeのアップグレードは以前の20%の工数で済むようになり、チームはネイティブ問題の対処ではなくプロダクトに集中できるようになりました。
運用面でも、Target API level要件のメールが以前ほどの危機感を呼ばなくなりました。
パフォーマンスボトルネックへの取り組み
ネイティブ依存の整理が終わったら、JS側のパフォーマンス問題に取り組みました。取り組む前に進捗を測るためのベンチマークが必要です。Maestroで典型的なユーザーフローを作成し、Flashlightを使ってJSとネイティブスレッド使用量をカバーするLighthouseベースのパフォーマンススコアを取得しました。
以下は、カテゴリ詳細ページに遷移し、ページングする典型的なMaestroスクリプトの抜粋です:
- launchApp
- assertVisible : "Entdecke dein DEPOT"
- tapOn : "Deko & Wohnen"
- tapOn : "Kerzen & Lichtobjekte"
- tapOn : "Kerzen"
- tapOn : "Stumpenkerzen"
- waitForAnimationToEnd
- assertVisible : ${ PRODUCT_TITLE }
- swipe : start : 50%, 75% end : 50%, 25% duration : 40
- assertVisible
(スクリプトはリストページを素早くスクロールし、最初の製品タイトルの可視性を確認するよう設計されています。)
以上が、DEPOTの事例を通じてExpo Prebuildを用いてモバイル小売アプリをモダナイズした流れと得られた効果の概要です。技術的負債の削減と自動化により、アップグレードコストを劇的に下げ、パフォーマンスと開発効率を回復できました。