子育てアプリを「PWA」で作った理由
親子で使うタスク&ごほうび管理アプリ「RingiQuest」を、ネイティブアプリではなく PWA(Progressive Web App)として作りました。個人開発でこの選択をした理由、Cloudflare Workers 1つで完結させた構成、そして実際にぶつかった制約——iOS のプッシュ通知、Web Push の自前実装、AI のコスト設計で踏んだ落とし穴までを、包み隠さず書きます。
App Store の審査を待ちたくなかった
このアプリは、自分の家族のために作りはじめたものです。中学2年生の息子の夜更かしをなんとかしたい、という個人的な動機が出発点でした(その経緯は別の記事に書いています)。
そういう作り方をすると、開発のサイクルが独特になります。夕食のときに「ここが使いにくい」と言われて、その夜に直す。翌朝には直ったものが使える。この速度が出せないなら、家族は使ってくれません。
個人開発でネイティブを選ぶコスト
ネイティブアプリでこれをやろうとすると、いくつもの関門があります。
- iOS と Android で、それぞれ別のコードとビルド環境
- 開発者アカウントの年額費用
- ストアの審査(修正のたびに待ち時間が発生する)
- ユーザー側の「インストールする」という決断
最後のひとつが、個人的にはいちばん重いと考えました。まだ価値が分からないアプリを、わざわざストアからインストールしてくれる人はほとんどいません。まして、それが知らない個人が作ったものであればなおさらです。
「明日直したい」に応えられるか
審査があると、緊急の修正にも数日かかることがあります。家族向けの小さなアプリで、ポイント計算にバグが見つかったとして、修正版が使えるようになるのが来週です、というのは受け入れがたい。
Web ならデプロイした瞬間に反映されます。この一点だけでも、選ぶ理由としては十分でした。
PWA を選んだ4つの理由
理由1: インストールの障壁がゼロ
PWA は URL です。「これ使ってみて」とリンクを送るだけで、相手はもう使えます。気に入ったらホーム画面に追加してもらえばアプリのように起動しますが、それは後からでかまいません。
一般公開したいと考えていたので、この差は決定的でした。ストアの検索結果で見つけてもらうより、記事や口コミからリンクをたどってもらうほうが、個人開発には現実的です。
理由2: iOS / Android / PC を1つのコードで賄える
主な利用端末はスマートフォンですが、保護者が PC から設定を見たい場面もあります。1つのコードベースで全部を賄えるのは、個人で保守するうえで大きい。
理由3: 更新が即時に反映される
前述のとおりです。GitHub の main ブランチにマージすれば、CI がデータベースのマイグレーションを適用してからデプロイします。数分後には本番に出ています。
理由4: ストアの手数料も審査もない
このアプリは完全無料で、アプリ内課金もありません。ストアに置く経済的な理由がそもそもありませんでした。
加えて、子ども向けのアプリをストアに出す場合、各ストアのファミリー向けポリシーへの対応が必要になります。それ自体は正しい規制ですが、個人開発で最初に向き合うにはコストが高い。Web で公開し、自分でプライバシーポリシーを整備するほうが、結果として誠実にやれると判断しました。
それでも PWA が抱えている現実的な制約
いいことばかり書いても仕方がないので、実際にぶつかった制約を書きます。PWA を検討している人は、ここだけは事前に知っておいたほうがいいと思います。
制約1: iOS のプッシュ通知は「ホーム画面に追加」が必須
これが最大の制約です。iOS では、Safari でページを開いているだけでは Web Push を受け取れません。ホーム画面に追加して、そこから起動した状態でないと、通知の許可すら求められないのです。
RingiQuest にとって通知は重要な機能です。稟議の決裁依頼が届いたこと、クエストの期限が近いこと、朝と夜の応援メッセージ。これらが届かないと、体験がかなり削られます。
対策としてできることは、案内を丁寧にすることくらいでした。使い方ガイドに iOS 向けの手順を明記し、アプリ内でも通知設定の前に案内を出しています。それでも、ここで一定数の利用者が離脱するのは避けられないと見ています。
制約2: 通知許可までの導線が長い
ネイティブアプリなら、インストール直後に通知許可のダイアログを出せます。PWA では「ホーム画面に追加 → そこから起動 → 設定を開く → 通知を有効にする」という手順を踏んでもらう必要があります。
手順が1つ増えるごとに、完了する人は減ります。ここは PWA の構造的な弱点です。
制約3: 「アプリを入れる」という一般的な感覚とのズレ
技術者には自明でも、一般の利用者にとって「ブラウザで開いてホーム画面に追加すると、アプリみたいに使える」というのは、まだ当たり前の知識ではありません。
LP に「インストール不要」と大きく書いていますが、これがメリットとして伝わるか、それとも「ちゃんとしたアプリじゃないのでは」という不安になるかは、正直まだ判断がついていません。
PWA を選ぶ前に確認すること プッシュ通知がプロダクトの中核機能なら、iOS の制約は事前に必ず検証してください。「あとで何とかなる」部分ではありません。逆に、通知が補助的な役割なら、PWA の弱点はほとんど問題になりません。
技術構成——Cloudflare Workers 1つで完結させた
構成の方針は明確でした。管理するインフラの数を、可能な限り減らす。個人開発で継続的に保守することを考えると、構成要素の数がそのまま将来の負債になります。
Workers + Static Assets で、SPA と API を同居させる
1つの Cloudflare Worker が、React の SPA を配信し、同時に /api/* へのリクエストも処理します。デプロイは wrangler deploy の一発で完結します。フロントとバックエンドを別々にデプロイする必要がありません。
新規プロジェクトでは Pages ではなく Workers が推奨されており、静的アセットの配信も Workers に統合済みです。今から始めるなら、こちらが素直だと判断しました。
データ層: D1 + Drizzle ORM
Cloudflare の SQLite ベースのデータベース D1 に、Drizzle ORM を組み合わせています。スキーマ定義から SQL のマイグレーションを生成でき、wrangler d1 migrations と噛み合います。
運用で重要だったのは、複数テーブルの更新を必ず1つのバッチで原子化することでした。ポイントの付与、クエストの完了、稟議のステータス更新。これらが途中で失敗して片方だけ書かれると、家庭内のポイント経済が壊れます。
関連して、設計上の判断をひとつ。ポイントの残高カラムを作りませんでした。残高も累計も、すべて元帳(point_ledger)から都度導出します。カラムを持つと、必ずどこかで実際の履歴とズレます。金額を扱うなら、集計値を保存しないほうが安全です。
API: Hono の RPC で型を共有する
API フレームワークは Hono です。Workers 上でネイティブに動き、非常に軽量です。
採用の決め手は RPC モードでした。サーバー側で定義した型が、そのままクライアントで使えます。コード生成のステップを挟まずに、API の型安全性が得られる。個人開発では、この「手順が1つ減る」ことの価値が大きい。
バリデーションは Zod で書き、スキーマを client と server で共有しています。API の仕様が変わったとき、直す場所が1か所で済みます。
選ばなかった構成と、その理由
- Next.js。 アプリ本体は認証必須なので、SSR も SEO も要りません。Edge Runtime の制約に付き合うコストに見合いませんでした。一般公開用のランディングページは、こうして静的ページとして別に作れば足ります。
- SSR 前提のフレームワーク全般。 成立はしますが、今回の要件には抽象化が過剰でした。Vite と Hono の組み合わせのほうが薄く、速い。
-
モノレポツール。
パッケージが1つで足りる規模です。
client/server/sharedのディレクトリ分割で十分に関心を分離できました。
Web Push を自前で実装することになった話
この記事でいちばん技術的に泥臭い部分です。
既存ライブラリが Workers 上で動かなかった
Web Push を実装しようとして、既存のライブラリを順に試しました。結果は全滅でした。Node.js の暗号 API に依存しているものが多く、Workers のランタイムでは動きません。
選択肢は2つ。通知機能をあきらめるか、自分で書くか。通知はこのアプリの中核だったので、書くことにしました。
RFC 8291 の暗号化を書く
Web Push のペイロードは、RFC 8291 で定められた方式(aes128gcm)で暗号化して送る必要があります。鍵の共有、暗号化のパラメータ、ヘッダーの組み立て。仕様を読みながら実装しました。
こういうコードのつらいところは、間違っていても「送信は成功したように見える」ことです。暗号化がわずかにずれていても、リクエスト自体は通ります。ただ、通知が届かない。原因の切り分けが極端に難しくなります。
参照実装とのラウンドトリップテストで担保する
そこで、テストの方針を決めました。参照実装との相互運用性をテストで固定することです。
自分で暗号化したものを参照実装で復号できるか、逆に参照実装が暗号化したものを自分の実装で復号できるか。この往復が通ることを、テストとして常に走らせています。自分の実装同士でテストしても意味がありません——間違い方が一貫していれば、間違ったまま通ってしまうからです。
個人開発で暗号まわりを自前実装するのは、基本的におすすめしません。ただ、どうしても必要なら、参照実装との突き合わせだけは必ずやってください。
AI のコスト設計で踏んだ落とし穴
最後に、つい最近見つけた問題を書きます。個人開発で AI を使うなら、たぶん役に立ちます。
可用性のための設計が、コストの穴になっていた
このアプリには AI を使った機能がいくつかあります(対価ポイントの提案、応援メッセージ、クエスト提案、週次レポート、レートの見直し提案)。実行基盤は Workers AI です。
AI のモデルは予告なく廃止されることがあります。実際、開発中に一度その事態に遭遇しました。そこで、モデルを複数並べたフォールバックチェーンを組み、第1候補が使えなければ次を試すようにしました。可用性の設計としては、ごく普通です。
問題は、そのチェーンの2番目に、約6倍高価なモデルを置いていたことでした。
「請求書でしか気づけない」状態になっていた
第1候補が廃止されると、システムは自動的に高価なモデルへ落ちます。機能は正常に動き続けます。エラーも出ません。画面上、何も変わりません。
つまり、コストが数倍になったことに気づく手段が、月末の請求書しかない状態でした。可用性のために入れた仕組みが、そのままコスト事故の導線になっていたわけです。
どう直したか
対処は3つです。
- チェーンから高価なモデルを完全に外した。 「最後の砦」としても残しませんでした。このアプリの AI 機能はすべて決定論的なフォールバック(計算式や定型文)を持っているので、AI が使えないことは、すでに設計された正常な縮退です。安価なモデルが全部落ちている状況は、6倍払う場面ではありません。高価なモデルを最後に置くこと自体が、リスクの再導入でした。
- 第1候補以外が応答したら、警告ログを出すようにした。 これが本質的な修正です。従来は全モデルが失敗したときしかログが出ず、第1候補が死んで第2候補が答えている状態は無記録でした。
- 単価の上限を、単体テストで機械的に固定した。 チェーンに入っている全モデルが単価表に載っていること、上限以下であることをテストで検査します。コメントに「高いモデルを入れるな」と書くだけでは、いつか誰か(未来の自分)が入れます。
教訓 フォールバックは「動き続けること」を保証しますが、「同じコストで動き続けること」は保証しません。縮退したことが観測できない設計は、壊れていないのと同じではありません。フォールバック先の単価は、可用性の設計と同時に決めるべきでした。
PWA で個人開発をする人へ
向いているケース
- リンクを送って試してもらう形で広めたい
- 頻繁に更新したい、すぐ直したい
- iOS / Android / PC を1人で保守したい
- 課金がない、またはストア外決済で問題ないもの
向いていないケース
- プッシュ通知が体験の中核で、iOS の利用者が多い
- カメラ・Bluetooth・バックグラウンド処理などを深く使う
- ストアの検索流入を主な集客経路にしたい
最初に決めておくとよいこと
経験から3つ挙げます。
- Service Worker の検証方法。 開発サーバーでは Service Worker が無効になっている構成が多く、通知やオフライン動作は本番相当のビルドでしか確認できません。この検証フローを最初に確立しておかないと、後半で必ず詰まります。
- 外部サービスが未構成でもアプリが動くこと。 RingiQuest では、通知の鍵も、AI も、メール送信も、未設定のまま全機能が正常に動きます。これは開発とテストの速度に直結しました。「あの環境変数がないと起動しない」は、個人開発では思った以上に足かせになります。
- データを持ち出せる仕組み。 利用者のデータを預かるなら、エクスポートと削除は最初から入れておくべきです。後から足すと、テーブルが増えるたびに漏れます。
まとめ——技術選定は、誰に届けたいかで決まる
PWA を選んだのは、技術的に優れているからではありません。「リンクを送るだけで使える」という性質が、このアプリの届け方に合っていたからです。
知らない個人が作った子育てアプリを、わざわざストアからインストールしてくれる人は多くありません。でも、記事を読んで興味を持った人がリンクをタップして、その場で試してみることはあります。この差を取りに行くために、iOS の通知まわりの制約は受け入れる価値がありました。
技術選定に一般解はありませんが、判断の軸ならあります。誰に、どういう経路で届けたいか。それが決まっていれば、選択肢は自然に絞れます。
RingiQuest を触ってみる この記事で書いた構成で動いているアプリです。ブラウザで開けばそのまま使えます。完全無料・アプリ内に広告なし。
RingiQuest を無料で試す