
Summary
この文書の要点
- 下書き、送信待ち、送信済み、失敗を状態として分ける。
- IndexedDBなどに保存するデータの範囲を絞る。
- 下書き、送信待ち、同期中、同期済み、競合、失敗を明示的な状態として扱う。
- IndexedDBにはサーバーIDだけでなく、ローカルID、schema version、更新時刻を持たせる。
どこが設計の難所か
オフラインでも入力できるフォームでは、通信復旧時に同じ内容が二重送信されたり、古いデータで上書きしたりする可能性があります。単にローカルへ保存するだけでは不十分です。
ブラウザのローカル保存は端末依存であり、永続性や容量に限界があります。個人情報や機密情報を長く残すことにも注意が必要です。同期できなかった時の利用者への説明も必要です。
PWAでオフラインフォームを作ると、ネットワークがない場所でも入力できます。しかし同期時には、サーバー側の変更、入力の重複、バリデーション変更、途中失敗が起きます。単純な再送キューでは説明しきれません。
境界をどう切るか
フォーム状態は、下書き、送信待ち、送信中、送信済み、失敗に分けます。サーバー側にはクライアント生成IDを渡し、再送しても同じ登録として扱えるようにします。
設計では、ローカル下書きとサーバー確定データを分けます。利用者が入力した内容はまずIndexedDBへ保存し、接続復帰後に同期キューとして送ります。サーバー応答によって同期済み、競合、修正待ちへ状態遷移させます。
実装で効く細部
React側ではIndexedDBに必要最小限の入力内容と状態を保存します。オンライン復帰時に送信待ちを順番に処理し、成功時はサーバーIDを記録します。失敗時は理由と再試行ボタンを表示します。
React側では、フォーム状態、永続化状態、同期状態を分けます。IndexedDBにはlocal_id、server_id、payload、schema_version、last_modified_at、sync_statusを保存し、同期処理はバックグラウンドでも手動でも実行できるようにします。
- ローカル保存成功とサーバー登録成功を同じ完了表示にしない。
- schema変更時に古い下書きをどう移行または破棄するかを決める。
- 同期失敗は利用者が再試行、編集、破棄を選べる状態にする。
壊れ方を観測する
検証では、入力途中でオフライン、送信中に切断、復旧後の再送、同じ下書きの二重送信、サーバー側で変更済みのデータとの競合を確認します。ブラウザを閉じても状態が残るかも見ます。
検証では、入力途中のオフライン化、送信直後の切断、複数タブ、古い下書き、サーバー側競合を確認します。Playwrightでネットワーク状態を切り替え、状態表示が誤解を生まないかを見ます。
捨てた選択肢とトレードオフ
オフライン対応を広げるほど状態管理は複雑になります。最初は下書き保存だけにし、自動同期は重要度が高いフォームに限定する判断もあります。完全なオフライン業務を目指すなら別設計が必要です。
オフライン対応を深くするとUXは強くなりますが、競合解決とデータ移行の複雑さが増えます。すべての機能をオフライン化するのではなく、現場で本当に必要な入力だけに絞る判断が重要です。
現場に残す判断軸
PWAのオフライン入力は、保存機能ではなく同期設計です。状態、冪等性、競合を明示すれば、一時的な通信不安定にも耐えられるフォームになります。
PWAのオフラインフォームでは、保存処理より状態設計が重要です。利用者が今どこまで確定しているかを理解できると、ネットワークが不安定な環境でも安心して使えます。


