😱

保守を1年半サボったExpo製のアプリを、最新化してリリースするのに半年かかった話

に公開

以前リリースした際の記事はこちらです👇

https://zenn.dev/wheatandcat/articles/20220503-memoir

リリース後は1年ほど更新していましたが、実装したかった機能が一通り揃ったところで更新をサボっていました。

久々に追加したい機能を思いついて開発を再開したところ…結果的に半年かけて最新化&リリースすることになったので、その軌跡をまとめます。

結論から

Expo SDK 48 → 53 へアップデートしてリリースした結果はこうなりました 😇

  • 修正ファイル数: 1,057
  • 追加行数: 41,998
  • 対応した issue & PR: 78件

👉 更新前から更新後の差分
👉 対応したissue & PR一覧


バージョンアップを対応したアプリの紹介

アプリ

ダウンロードはこちら。

■ iOS
https://apps.apple.com/jp/app/memoir/id1613532672

■ Android
https://play.google.com/store/apps/details?id=com.wheatandcat.memoir

■ LPサイト
https://memoir-app.dev

■ GitHub
https://github.com/wheatandcat/memoir

制作者

コンセプト

リリース時にアプリの紹介記事を書いたので、そちらを参照。

https://zenn.dev/wheatandcat/articles/20220503-memoir

使用している技術

※この記事を読む際の注意

Expoとは?

React Nativeをベースにしたモバイルアプリ開発フレームワーク。
EAS Buildなどの仕組みを通じて、ネイティブ実装を極力意識せずに開発・配布できる強力なプラットフォームです。

https://expo.dev/

1年半サボるとどうなる?

結構な日数開発をサボっていたのもあるので、コードもある程度トレンドに合わせて最新化しつつ1ヶ月くらいでリリースできれば良いかなーと気軽に思っていましたが、
調査の結果、それが甘い見積もりであることを思い知らされることになりました 😱

そもそもデバッグできない編

1. EAS Buildでビルドできない

まず最初にアプリがビルドできませんでした 🤮

ExpoはEAS Buildを使用してビルドします。
ExpoにBuild用のサーバーがあり、ビルド用のコードをアップロードすることでビルドされる仕組みになっている。
そのためローカルにビルドに必要な環境を構築しなくてもアプリが作成できるので非常に便利です。

https://docs.expo.dev/build/introduction/

ただ便利な仕組みが使える代わりに制約が強く最新のバージョンから2バージョン前までしかサポートしないことが多く、今回は最新バージョンがExpo SDK 53に対してアプリは48だった。
当然サポート外なのでサーバー経由ではビルドできなかった。

2. ローカルでも起動できない

正直サーバー経由でビルドできないのは想定していました。ただローカルでのビルドはできるだろうと思っていましたが、実際に試してみたらローカル環境でもビルドができませんでした 🤮

というのも以前のExpoはローカル環境はExpo Go[1]で起動、本番環境でリリースする場合はExpoサーバー経由でビルドが基本的な方式だった。
しかし、今はExpo Goはあくまで簡易的な開発ビルドの立ち位置で、ログイン機能やPush通知などのネイティブに関わる機能が実装されている場合はEAS Build[2]を使用する必要がありローカルでも起動できない状態になっていた。

https://expo.dev/go

3. Expoのライブラリが非推奨に

memoirではアプリをログインする場合はAndroidはGoogleアカウントでログイン、iOSはAppleアカウントでログインで実装している。
以前はGoogleログインをする場合は、expo-auth-sessionexpo-auth-session/providers/googleを使用していた。
しかし、現在は非推奨になっており別の方法に移行する必要があることを知った。

その他のExpoが提供するライブラリでも廃止、仕様変更が発生しており、それらを修正する必要がある。

4. Reactのバージョン問題

Expo SDK 48→53の間でReactが 18→19 に更新。
ReactのバージョンアップによるReact自体影響は少なかったが、1部のライブラリでReact 19で動作しないものがあり、移行は作業が発生。

代表的な例はRecoilで、以下のissueの通りでサポートを終了しており、React 19では動作しないことが報告されている。

https://github.com/facebookexperimental/Recoil/issues/2318

開発のトレンドも変わるよね編

1. 推奨ナビゲーションライブラリの変更

以前Expoで開発する場合のナビゲーションライブラリはreact-navigationがもっとも多く使用されてた。

現在はExpo Router公式のナビゲーションライブラリが提供されており、こちらが推奨。

https://docs.expo.dev/router/introduction/

しかも、Expo Routerファイルベースルーティング[3]を採用しており、移行するにはフォルダ構造から変更する必要がある。

2. ディレクトリ構造のトレンドが変わる

以前はフロントエンドのディレクトリ構造はAtomic Designが流行っており、本アプリでも採用していた。

https://atomicdesign.bradfrost.com/chapter-2/

しかし、Atomic Designは長期の開発でのメンテナンス性に難があり、今のトレンドでは無い。

今のトレンドはFeature-Sliced DesignScreaming Architectureのような普遍的な概念を取り込んだディレクトリ構造で開発するのが主流です。

https://feature-sliced.design/

https://dev.to/profydev/screaming-architecture-evolution-of-a-react-folder-structure-4g25

3. Linter、Formatterのトレンドが変わる

以前はLinter、FormatterはESLintPrettierを使用していた。

現在の上記の代わりにBiomeを採用するケースが増えている。

https://biomejs.dev/

特に個人開発ではBiomeのようにシンプルな設計のものを採用するのは、開発作業に集中しやすいのでメリットも大きい。

プラットフォームも変わっているよ編

1. edge-to-edgeが推奨

AndroidのTargetSDKを35にするとAndroid 15以降の端末ではedge-to-edgeが強制的に有効になるため対応が必須になる。

https://developer.android.com/develop/ui/views/layout/edge-to-edge?hl=ja

具体的にはAndroidのディスプレイの上下のStatusBarNavigationBarもアプリのレンダリング領域になるので、何も対応しないと以下の画像のように表示されてしまう。

参考画像1[4] 参考画像2[5]

2. Expoのapp.config.tsの書き方が変わる

Expoの基本設定はapp.config.tsに設定。

https://docs.expo.dev/workflow/configuration/

以前のapp.config.tsではhooksというプロパティに各ライブラリに設定するオプションを記載していたが、今はpluginsに記載する方式になった。
設定方法が変わっている箇所もあり修正が必要である。

3. Google Cloudでサポートされていなくなる

アプリでは無くバックエンドの話だが、バックエンドも若干修正が必要な箇所があったが、そこでも問題が発生。

Push通知を送信するようの処理を Google CloudのCloud Functionsで実装していたが、元々使用していたCloud Functionsの1世代目はサポート終了しており、以下の対応が必要になった。

  • Cloud Functionsの1世代目から2世代目のバージョンアップ
  • Goのバージョンアップ(Go 1.15 → Go 1.23)※2世代目ではGo 1.15をサポートしていない

https://cloud.google.com/functions?hl=ja

4.Play Storeのサポートバージョンが変わる

Play Storeから以下の通知が飛んできた。

本来は、単純にAndroidのgradleの設定を変更して再リリースすれば問題ない話だったが、上記のとおりビルドすらできない状態なので、これはピンチ 🫠

上記を踏まえて、どういう対応をしたのか?

方針

ちょっとした修正くらいで動く感じもしなかったので、もういっそ最新化してしまおうという方針で対応した!

最新化の構成の比較

対象 変更前 変更後
Expo SDK 48 53
ナビゲーション react-navigation expo-router
ディレクトリ構造 Atomic Design Screaming Architecture
Lint、Formatter ESLintPrettier Biome
グルーバルステート Recoil Zustand

各対応内容

デバッグができるようになるまで編

1. ローカルで起動できるように修正

Expoにはバージョンアップ時にマイグレーションを自動で行なうコマンドが用意されているので以下のコマンドを実行。

$ npx expo install expo@latest
$ npx expo install --fix

https://docs.expo.dev/workflow/upgrading-expo-sdk-walkthrough/

その上で、以下のコマンドを実行してローカルで起動できるか確認。

$ npx expo-doctor

https://docs.expo.dev/develop/tools/#expo-doctor

出力された警告を修正して以下のコマンドで実行。

$ npx expo run:ios

で、起動して欲しかったが、今回のケースは前述のとおり、根本から改修しないといけなかったので、当然起動しなかった 😓

なので、以下のドキュメントを参考にExpo Routerのサンプルアプリを作成して、それをベースに徐々に既存のコードを移行していった。

https://docs.expo.dev/router/installation/

ここの修正PRはコチラ

詳細な作業内容は以下のブログに記載。
👉 Expoアプリ最新化①:SDKアップデートとexpo-routerへの移行

2. ディレクトリ構成の修正

更新前

Atomic Designをベースに以下のディレクトリ構造をしていた。

.
└── src
    ├── components
    │   ├── atoms
    │   ├── molecules
    │   ├── organisms
    │   ├── pages
    │   └── templates
    ├── containers
    ├── hooks
    ├── img
    ├── lib
    ├── queries
    └─ store

更新後

ファイルベースルーティング & Screaming Architectureをベースに以下のディレクトリ構造に修正。

.
├── app
│   └── (app)
│       ├── items
│       ├── my-page
│       ├── search
│       └── settings
├── components
│   ├── elements
│   └── layouts
├── containers
├── features
│   ├── home
│   │   └── components
│   └── search
│       ├── components
│       └── hooks
├── hooks
├── lib
├── queries
└── store

3. ログインできるように

ローカルで起動できるようになったので、次はログイン機能を修正。

既存のexpo-auth-session/providers/googleでのGoogleログインはサポートされなくなったので、@react-native-google-signin/google-signinを使用した実装に移行。

https://react-native-google-signin.github.io/docs/setting-up/expo

最終的には以下の画像のとおり問題なくログインできるようになった。

ログイン

ここの修正PRはコチラ

詳細な作業内容は以下のブログに記載。
👉 Expoアプリ最新化②:Expo SDK52でGoogleログインを実装

4. EAS Buildでビルドできるように

本番環境のアプリを作成して検証が行えるようにEAS Buildを使用したビルドを行えるように修正した。

https://docs.expo.dev/build/introduction/

ローカルビルドでは問題なかったが、app.config.tsの書き方が正しくないとエラーになっていたので最新のドキュメントを確認してビルドできるようになった。

ここの修正PRはコチラ

検証の準備編

1. Linter、Formatterの修正

検証開始後にLinter、Formatterで悩みたくなかったので、先にESLint & PrettierからBiomeに移行した。

https://biomejs.dev/

2. CIの復活

CIでテスト、Linttype checktestを行っていたが、GitHub Actionsで使用していたモジュールのバージョンが非推奨になっており動作してなかったので修正した。

ここの修正PRはコチラ

3. Recoil → Zustandへの移行

RecoilはReact 19で動作しないので、Zustandに移行した。

https://zustand-demo.pmnd.rs/

ここの修正PRはコチラ

詳細な作業内容は以下のブログに記載。
👉 Recoil→Zustandに移行

4. Storybookの復活

検証中のUI修正に使用するためにStorybookを復活。
Storybook v6からv8にアップデートして動作可能になった。

https://storybook.js.org/

ここの修正PRはコチラ

詳細な作業内容は以下のブログに記載。
👉 expo-routerを使ったアプリでstorybook v8を導入

5. Sentryの復活

検証中の不具合の調査に使用するためにSentryを復活。
元はexpo/sentry-expoを使用して実装していたが、現在は非推奨だったため@sentry/react-nativeに移行した。

https://sentry.io/

ここの修正PRはコチラ

詳細な作業内容は以下のブログに記載。
👉 Expo SDK 52でSentry導入

検証編

1. Qaseで管理しているテストケースを実行

memoirではQaseを使用して手動のテストケースを管理していた。

https://qase.io/

以下にQaseのテストケース管理画面を載せておく。

2. バグを見つけ次第issue作成

以下のissueにSub-issueを作成して管理。

https://github.com/wheatandcat/memoir/issues/327

検証で洗い出したバグは 28件。その一部を以下で紹介。

3. 主な修正したバグ

スプラッシュスクリーンのサイズがおかしい

  • スプラッシュスクリーンが異常に小さく表示される
  • 以下のissueの通りでスプラッシュスクリーンの表示で使用しているexpo-splash-screenがフルサイズのスクリーンの画像をサポートしなくなったので指定する画像を修正
バグ発生時 改修後

iOSでreact-native-picker-selectが動いていないので修正

  • iOSでreact-native-picker-selectをタッチしてもドロップメニューが表示されない
  • 以下のissueに記載の通りExpo SDK 52から発生。コメントに記載の通りで{ inputIOSContainer: { pointerEvents: "none" } }を追加することで解消した
バグ発生時 改修後

iOSで日付編集ができない

  • DatePickerを動かすとUIが閉じてしまう
  • iOSのバージョンアップの影響だったのでreact-native-modal-datetime-pickerを最新にして書き方を修正したら解決した
参考画像

ローカルPush通知が設定できていない

  • Expo SDK48→53の間でexpo-notificationsでのWeeklyでローカルPush通知を実装する書き方が変わっていたので、最新の書き方に修正したら解決した
参考画像

iOSの曜日設定のドロップメニューの色が誤っている

  • iOSでダークモードをONにすると発生
  • 既存で使用していたreact-native-picker-selectがダークモードをサポートした影響で発生
  • 以前はダークモードをサポートしていなかったので自前でCSSをカスタマイズして対応していたが、バージョンアップ時にそのせいで表示がおかしくなっていた
  • propsのdarkThemeを正しく設定することで解決
バグ発生時 改修後

タスク登録の入力画面を開いたときにテキスト入力にフォーカスが設定されていない

参考画像

iOSで振り返りの共有機能が動作していない

参考画像

Androidのedge-to-edge起因で発生したバグ

バグ発生時 改修後

Androidで「今週のmemoirを確認する」とナビゲーションボタンが重なってタッチできない

  • こちらも根本原因はedge-to-edge
  • Androidの「3ボタン ナビゲーション」に設定すると、ナビゲーションボタンとUIが重なってタッチできないケースがあった
  • react-native-safe-area-contextから取得した値でbottomの位置を調整をして修正
バグ発生時 改修後

バグ対応を終えて

上記のバグ改修が完了して、2025年8月16日に無事memoir v1.9.0としてストアにリリースできました!

まとめ

Expoは更新をサボると辛い

素のReact NativeのバージョンアップはiOS/Androidのネイティブコードを修正しなくてはならないケースが多く辛いです。
それに比較すると一般的にはExpoを使用することでネイティブコードを修正する必要がなくなるので、バージョンアップの負担が軽減されます。

ただ上記の条件が当てはまるのはExpoの最新を追っている状態を保てている場合の話で、年単位で開発期間が空いた場合は、むしろプラットフォームの縛りが強い分バージョンアップ作業が大変になりました 🥲

今回のような愚直な対応方法ではなくBare Workflowに移行してExpoのEAS Buildに依存しないようにしてビルドするなどの迂回策もあります。

https://docs.expo.dev/bare/overview/

とは言え、フルにExpoのプラットフォームを活用するなら最新をキャッチアップしているのは前提になるので、サボってはいけないなと反省。

AIでのバグ改修も有効

edge-to-edge周りのバグ改修ではClaude Codeが思った以上に役に立ちました。
例えばAndroidでダイアログを開くとbottomが不自然になっているのバグ対応は、自分でやってみて原因が分からなかったので、試しにClaude Codeにissueを渡して対応させてみました。
そしたら、ModalコンポーネントのpropsにstatusBarTranslucentがあるのを見つけてきて、それを設定することでedge-to-edgeが有効な場合でも、正常にレンダリングできるようになりました。

似たような感じで、edge-to-edge系の対応はClaude Codeで結構対応できました。プロジェクト固有では無いバグ対応にはAIは、かなり有効でした!

https://docs.anthropic.com/ja/docs/claude-code/overview

最後に

「アプリは何もしなくても壊れる」ことを身をもって体験しました 😱

脚注
  1. Expoで開発中のアプリをローカルや実機で起動するためのクライアントアプリ ↩︎

  2. Expoが提供するビルド環境でExpoでのアプリを簡単にビルドサービス ↩︎

  3. ファイルベースルーティングとは、ファイルのパスによってルーティングを決定する方式。Next.jsやNuxt.jsでも採用されている ↩︎

  4. StatusBarとヘッダーが重なって表示 ↩︎

  5. 「3ボタン ナビゲーション」を表示する設定にしている場合に、「ナビゲーションボタン」とbottomに表示しているボタンが重なって表示されている ↩︎

Discussion