Thynth
← Works Log

Next.js×Capacitor!TokStopの構成

Next.js、Supabase、Capacitorを組み合わせたスマホアプリ「TokStop」の実践的な技術スタックと、セッション重複防止などの実装ノウハウを公開します。

導入

スマートフォンの使いすぎを防ぐデトックスアプリTokStopの開発において、私たちは「Webの生産性」と「ネイティブアプリの体験」を両立させるために、Next.js + Supabase + Capacitor というモダンな技術スタックを採用しました。

本記事では、このハイブリッドアプリ構成のアーキテクチャ詳細と、開発中に直面した認証やセッション管理の課題、そしてそれをどのように解決したかを余すことなく公開します。モバイルアプリ開発の新しい選択肢として、ぜひ参考にしてください。

解決したい課題

Web技術(React/Next.js)で素早く開発しつつ、iOS/Androidストアで配信可能なアプリを構築する際、以下のような課題に直面しました。

  1. 認証のシームレスな統合: Webとネイティブアプリの双方で、SupabaseのメールMagic Link認証をスムーズに機能させること。
  2. ネイティブ機能の活用: スマホの使用検知やタイマー状態の永続化など、ネイティブ特有の処理をWebビュー側と同期すること。
  3. APIの二重決済・二重処理防止: ネットワークが不安定なモバイル環境で、セッション終了APIが重複して呼ばれた際のデータ整合性。

これらの課題を解決するためのアーキテクチャと実装の工夫を紹介します。他の開発プロセスについては、制作ログ一覧からもご覧いただけます。

使用したプロンプト

開発の設計段階や、AIアシスタントとの壁打ちで使用したプロンプトは以下の通りです。

Next.js + Supabase + Capacitor でモバイルファーストのデトックスアプリを作るアーキテクチャ解説を書いて。
WebアプリをCapacitorでiOS/Androidに載せ、Magic Link認証とセッションAPIをSupabaseで回す構成を説明して。

これらのプロンプトを通じて、Webファーストでありながらネイティブアプリとしても堅牢に動くハイブリッド構成の土台を固めました。

工夫した点と失敗談

失敗談1:大雑把なプロンプトによる設計の破綻

当初、AIに対して「Next.jsとCapacitorでアプリを作って」と大雑把に指示してしまいました。その結果、出力された設計は完全にWebブラウザ前提のものであり、ネイティブアプリ化(Capacitor)した際のMagic Link認証のリダイレクト(ディープリンク)や、ローカルストレージの挙動が考慮されていませんでした。

対策: プロンプトを「Next.js + Supabase + Capacitor構成で、iOS/AndroidのMagic Link認証とAPIの重複防止を考慮したアーキテクチャを設計して」と具体化し、モバイル固有の仕様(カスタムURLスキームやディープリンク)を最初から組み込むように修正しました。

失敗談2:ネイティブ機能の過信と機能差

Web MVPの段階で「スマホの使用検知やプッシュ通知」を簡単に実装できると見込んでいましたが、いざCapacitorでビルドすると、OS側のバックグラウンド制限によりWebビュー単体では動作しないことが判明しました。

対策: Capacitorのネイティブプラグインを導入し、バックグラウンドでの画面状態やセンサー情報をネイティブ層で処理。Web側とネイティブ側での機能差をドキュメント化し、段階的に統合するアプローチへと切り替えました。

工夫した点:重複報酬を防ぐセッションAPI設計

最もこだわったのは、セッション停止時(/api/session/stop)の重複報酬防止です。電波状況の悪いモバイル環境では、ユーザーが「ストップ」ボタンを連打したり、リトライ処理が走ることで、APIが二重に呼び出され、ポイントが二重付与されるリスクがありました。

この競合を防ぐため、データベース(Supabase/PostgreSQL)側で以下のように「status = 'running' の行のみを更新する」という楽観的ロックのようなパターンを適用しました。

// サーバーサイド(Next.js API Route)での処理イメージ
const { data, error } = await supabase
  .from('sessions')
  .update({ status: 'stopped', ended_at: new Date() })
  .eq('id', sessionId)
  .eq('status', 'running'); // 実行中のセッションのみを対象にする

これにより、2回目のリクエストが到達した時点ではすでにステータスが stopped になっているため更新がスキップされ、重複して報酬が付与される不具合を完全に防ぐことができました。また、タイマー状態は localStoragetimer-store)にも併用保存し、アプリが強制終了しても再開できるように工夫しています。

完成物の評価

Next.js + Supabase + Capacitor によるハイブリッドアーキテクチャは、極めて高い開発効率を実現しました。 コードベースの9割以上をWebと共通化しながら、Codemagicを用いたCI/CDによって、iOS/Androidそれぞれのネイティブバイナリをスムーズにビルドできています。

特にSupabaseをバックエンドに据えたことで、リアルタイムのデータ同期やMagic Linkによるパスワードレス認証が容易になり、ユーザー体験の向上に直結しました。重複報酬防止のAPI設計も本番環境(v1.0.14)で安定して稼働しており、堅牢なデトックスアプリを構築することができました。