月額0円で動く学習管理アプリを個人開発した話 — React PWA + Cloudflare 構成のすべて
TODO・ポモドーロ・学習統計を統合した PWA「adalab focus」の技術構成を解説。React 19 + Vite + Dexie のローカルファースト設計と、Cloudflare Pages Functions + D1 によるサーバーレス同期で、運用コストゼロを実現した方法を紹介します。
adalab focus 開発記
学習管理 PWA「adalab focus」の開発で得た知見を解説。ローカルファースト設計、オフライン同期、サーバーレス認証、タイマーの状態設計、ブラウザ拡張との連携まで
月額0円で動く学習管理アプリを個人開発した話 — React PWA + Cloudflare 構成のすべて
学習管理アプリ「adalab focus」を公開しました。TODO・ポモドーロタイマー・学習記録・統計・試験カウントダウンを 1 つの画面に統合した、いわば「勉強専用の TickTick」のようなアプリです。
この記事から数回に分けて、開発で得た技術的な知見をシリーズとして書いていきます。第 1 回はアーキテクチャ全体の話です。
個人開発アプリに課した3つの条件
作り始める前に、自分の中で譲れない条件を決めました:
1 と 2 は普通に考えると矛盾します。オフライン動作はデータを端末に置くことを要求し、同期はデータをサーバーに置くことを要求するからです。これを両立させるのがローカルファーストという設計です。
全体アーキテクチャ
ポイントは、アプリは常に IndexedDB だけを見て動くことです。画面の描画にサーバーは一切関与しません。サーバーは「他の端末との合流地点」でしかなく、落ちていてもアプリは普通に使えます。
技術選定の理由
| 技術 | 役割 | 選んだ理由 |
|---|---|---|
| React 19 + Vite | UI | HMR が速い。素の SPA で十分(SSR 不要) |
| Tailwind CSS v4 | スタイル | デザインシステムを CSS 変数で一元管理 |
| zustand | UI 状態 | タイマーなど「DB に置かない状態」専用 |
| Dexie | データ層 | IndexedDB の薄いラッパー。useLiveQuery が強力 |
| Cloudflare Pages | ホスティング | 静的配信 + Functions が同居。無料枠が広い |
| Cloudflare D1 | 同期 DB | SQLite 互換。無料枠 500 万行読み取り/日 |
SSR を捨てた判断
Next.js も検討しましたが、採用しませんでした。学習管理アプリはログイン後の自分専用画面しかなく、SEO もファーストビューの速度も問題になりません。それよりも「Service Worker でアプリ全体をキャッシュして、2 回目以降はネットワークゼロで起動する」方が体験に効きます。
PWA 化は vite-plugin-pwa でほぼ設定だけです:
スマホでは「ホーム画面に追加」するとネイティブアプリのように全画面で起動します。
データの流れ:書き込みは常にローカルへ
タスク追加を例にすると、処理は徹底して「ローカル完結 + 後送り」です:
UI 側は Dexie の useLiveQuery で IndexedDB を購読しているので、db.tasks.add した瞬間に画面が更新されます。サーバーへの送信が成功したかどうかは UI と無関係です。
この構成の気持ちよさは、「ローディング中…」がほぼ存在しないことです。データは常に手元にあるので、画面遷移は全部一瞬で終わります。
ID は ULID にする
複数端末がオフラインで同時にデータを作る以上、ID はクライアント側で生成する必要があります。UUID でもよいのですが、ULID(時系列ソート可能な一意 ID)を使いました。
「作成順に並べたい」はアプリのあらゆる場所で出てくるので、ID 自体がソートキーになるのは地味に効きます。
運用コストの実際
公開から現在までの請求額は 0 円です。
| サービス | 無料枠 | 実際の使用量 |
|---|---|---|
| Cloudflare Pages | ビルド 500 回/月 | 1 日数回 |
| Pages Functions | 10 万リクエスト/日 | 数百〜数千/日 |
| D1 | 500 万行読み取り/日 | 数千行/日 |
ローカルファースト設計のおかげで API リクエスト自体が少ない(同期時しか飛ばない)ので、無料枠を使い切る未来がそもそも見えません。個人開発との相性は抜群だと思います。
まとめ
- ローカルファースト(IndexedDB が主、サーバーは従)にすると、オフライン動作・同期・速度の 3 つが同時に手に入る
- ログイン後専用の SPA に SSR は不要。PWA キャッシュの方が体験に効く
- Cloudflare Pages + Functions + D1 は個人開発の無料枠として現状最強クラス
次回は、この構成の心臓部である Outbox パターンによるオフライン同期の実装を詳しく解説します。「オフラインで編集 → オンライン復帰で自動同期」を、どうやって壊れないように作るかという話です。
リンク
- adalab focus — 今回作ったアプリ(無料で使えます)
- Dexie.js — IndexedDB ラッパー
- Cloudflare D1 — エッジ SQLite