はじめに
皆様、こんにちは。『ねむりのガチャ』アプリ開発記の第8回です。
前回は楽しいガチャシステムを実装し、アプリとしての機能がほぼ出揃いました。しかし、Webアプリ開発において「機能を作って終わり」ということは絶対にありません。完成に近づくにつれて、想定外の挙動や目に見えない不具合(バグ)との戦いが必ず始まります。
今回は、実際の開発終盤で直面した「ReactのUIバグ」と、過去の別プロジェクトでの経験が活きた「キャッシュ問題」という、2つのリアルな苦労話とその解決策を共有します。
モーダルが閉じない!?Reactステート管理の罠
アプリの完成間近、ユーザーがアカウントを削除(退会)する機能をテストしていたときのことです。
退会ボタンを押すと、裏側ではFirebaseのアカウント削除と、D1データベースのデータ消去が正常に完了し、ユーザーのステート(状態)は正しく未ログイン状態(null)に戻っていました。背景の画面もトップの「ログイン画面」に切り替わっています。
ところが、画面の手前に表示されていた「アカウント設定」のモーダル枠だけが、「処理中…」という文字を出したまま閉じずに残り続けるという奇妙なバグが発生しました。
原因は、Reactのステート管理の「漏れ」でした。退会処理が成功したあとに、モーダルを閉じるための setShowAccountSettings(false) を呼び出し忘れていたのです。さらに、コンポーネントが破棄(アンマウント)されたあとにステートを更新しようとするとメモリリークの警告も出てしまいます。
解決策として、App.tsx に useEffect を追加し、「user ステートが null になったら、強制的にモーダルを閉じる(setShowAccountSettings(false) を実行する)」という監視ロジックを組み込みました。これにより、退会時やログアウト時に必ずUIが綺麗にリセットされるようになり、バグを無事に撃退できました。
見えないキャッシュとの戦い(外部APIの教訓)
もう一つの壁は「データが最新に更新されない」という現象です。これは本アプリの直接のバグではありませんが、私が並行して開発・保守を行っていた別のサイト(実績一覧ページのRSS読み込み機能など)で経験した非常に厄介なトラブルでした。
そのサイトでは、外部サービス(rss2json.comなど)を経由してブログの新着記事(RSS)を取得していました。しかし、ブログを更新しても画面上の記事が数日間も古いまま反映されない事態が発生したのです。
原因は「外部サービス側の強力なキャッシュ」でした。こちらがいくらフロントエンドのコードを直しても、中継地点のサーバーが古いデータを返し続けていたのです。
この苦い経験から学んだのは、「外部サービスのキャッシュに依存しすぎない」ということです。
解決策として、Cloudflare Workersの中に自前のAPI(/api/news-feed)を構築し、サーバー側で直接元のRSSを取得して Cache-Control: max-age=300(5分間だけキャッシュする)と明示的に制御する仕組みを作りました。
今回の『ねむりのガチャ』アプリでも、この教訓を活かして、外部に依存せずCloudflareのエッジネットワーク内でデータを完結させるアーキテクチャ(D1とPages Functionsの組み合わせ)を採用したことで、データの反映遅延という悪夢を未然に防ぐことができました。
まとめ
今回は、開発終盤で直面したReactのUIバグと、キャッシュにまつわるアーキテクチャ設計の教訓についてお話ししました。
プログラムは書いた通りにしか動きませんが、通信やステート(状態)が複雑に絡み合うと、思いもよらない挙動を引き起こします。これらを一つずつ紐解き、論理的に解決していく「デバッグ」の過程こそが、エンジニアとしての腕の見せ所でもあります。
次回【第9回】は、サービス公開に向けて絶対に妥協してはいけない「セキュリティ強化と退会・データ完全消去機能」の実装についてお届けします。
【編集後記】
「処理中…」のまま画面が固まってしまったスクリーンショットを見たときは、「データは消えたのに、画面だけがおいてけぼりになっている!」と少し焦りました(笑)。
Reactのようなモダンなフレームワークは、画面の描画を自動で効率よく行ってくれる分、「データの状態(State)」と「画面の表示(UI)」の同期を開発者がしっかり設計してあげないと、今回のような幽霊モーダルが残ってしまいます。デバッグ作業は大変ですが、原因がわかってコードを修正し、意図した通りに画面がサッと切り替わったときの爽快感はたまりませんね!