Next.js で PWA オフラインキューを実装する

2026.02.02
Share:

はじめに

「オフラインでも使えるようにしてほしい」

IdeaSpool の開発中、自分自身がこの要望を強く感じました。

地下鉄に乗っているとき、ふとアイデアが浮かぶ。スマホを取り出してメモしようとしたら...圏外。「後でメモしよう」と思っても、電車を降りる頃には忘れている。

この問題を解決するために、Claude Code を使って PWA(Progressive Web App)のオフライン対応を実装しました。

ところが「オフラインでも動く」だけでは不十分でした。「オフライン中の操作をどこまで保持するか」という設計判断が必要になったのです。

この記事では、オフラインキューの設計で悩んだことと、最終的にたどり着いた「100件・7日・3回リトライ」という制限について共有します。

IdeaSpool とは

まず背景を説明させてください。

IdeaSpool は、アイデアをすばやくキャプチャして整理するツールです。思いついたアイデアをメモし、AI でタグ付けや分析を行い、プロジェクトにまとめていく。

このツールで最も重要なのは「キャプチャの速さ」です。アイデアは突然浮かびます。その瞬間にメモできないと、数分後には忘れてしまう。

だから「ネットワークに繋がらないから記録できない」という状況は、絶対に避けたかった。

なぜ PWA なのか

2026年1月19日の開発日記にはこう書きました:

PWA: モバイルでの爆速キャプチャを実現するため。ネイティブアプリ並みの体験を目指す

PWA を選んだ理由は3つあります。

1. インストールの手軽さ

ネイティブアプリはストアからダウンロードする必要があります。PWA は URL にアクセスするだけ。「ホーム画面に追加」を押せば、アプリのように使えます。

個人開発で Apple の審査を通すのは面倒ですし、Android と iOS の両方に対応するのも大変です。PWA なら1つのコードベースで済みます。

2. オフライン対応が可能

Service Worker を使えば、ネットワークがなくてもアプリを動かせます。キャッシュからリソースを読み込み、データは IndexedDB に保存する。

アイデアのキャプチャという用途では、これが決定的に重要でした。

3. Web の資産をそのまま活用

Next.js で作った Web アプリをそのまま PWA 化できます。新しいフレームワークを学ぶ必要がない。既存のコードをほぼそのまま使えます。

最初の実装:無制限にキューを貯める

Claude Code に依頼したオフライン対応の最初の実装は単純でした。

  1. オフライン中に行われた操作(作成・更新・削除)を IndexedDB に保存
  2. オンライン復帰時に、キューの操作をサーバーに送信
  3. 成功したらキューから削除

動きました。オフラインでアイデアをメモして、オンラインに戻ったら自動的にサーバーに同期される。

「完璧」と思った矢先、いくつかの問題に気づきました。

問題1:ストレージ容量

IndexedDB の容量はブラウザや端末によって異なります。無制限ではありません。

「1000件くらい大丈夫でしょ」と思うかもしれません。でも、各操作にはメタデータ(タイムスタンプ、操作タイプ、ペイロード)が付きます。アイデアの本文が長ければ、さらに容量を食います。

何も制限を設けないと、容量上限に達して新しいデータが保存できなくなるリスクがあります。

問題2:データの競合

もっと厄介な問題がありました。

1週間前にオフラインで編集したアイデアを、今更サーバーに同期するとどうなるか?

その間にサーバー側で同じアイデアが(別のデバイスから)更新されているかもしれません。古いデータで上書きしてしまうリスクがある。

「どちらが正しいか」を判断するロジックを入れることもできますが、複雑になります。そもそも1週間も前の操作を今更同期する必要があるのか?という疑問もありました。

問題3:同期処理の長時間化

キューが膨大だと、オンライン復帰時の同期処理が何分もかかります。

100件の操作を同期するなら、100回の API リクエストが発生します。それぞれに数百ミリ秒かかるとして、合計で数十秒〜数分。

その間、ユーザーはアプリを使えません。「同期中です...」という画面をずっと見せられることになる。これは UX として最悪です。

設計判断:3つの制限を設ける

これらの問題を解決するために、キューに制限を設けることにしました。

開発日記に悩みポイントを記録していました:

  • PWA オフラインキューの設計
    • どこまでキューに貯めるか
    • → 100 件、7 日間、3 回リトライの制限を設定

この3つの制限について、それぞれ説明します。

制限1:最大100件

キューに貯められる操作は最大100件。超過した場合は古いものから削除します。

「100件」という数字は、以下の計算から決めました:

  • 普通のユーザーが1日にキャプチャするアイデアの数: 5〜10件
  • オフラインが続く最大日数: 7〜10日(旅行や災害時を想定)
  • 10件 × 10日 = 100件

余裕を持って100件。これを超えるケースは稀でしょうし、超えたら古い操作は諦めてもらう。

制限2:7日間で失効

7日以上前の操作は自動削除します。

1週間以上オフラインが続くことは稀です。そこまで古い操作は、サーバー側のデータと競合するリスクが高い。

「もう諦めてください」という判断です。厳しいかもしれませんが、データの整合性を保つためには必要な制限だと考えました。

制限3:3回リトライ

同期に3回失敗した操作は、ユーザーに通知して手動解決を促します。

ネットワークの一時的な問題なら、3回以内に成功するはずです。3回失敗するということは、データそのものに問題がある可能性が高い。

たとえば、サーバー側で既に削除されたアイデアを更新しようとしている場合。いくらリトライしても成功しません。そういうケースはユーザーに通知して、「このデータは同期できませんでした」と伝える方が親切です。

実装:IndexedDB の基本

開発日記の学びにこう書きました:

IndexedDB はシンプルな用途なら生 API で十分(ライブラリ不要)

Dexie.js などのラッパーライブラリも検討しました。でも、オフラインキューの用途には生 API で十分でした。必要な操作は「追加」「取得」「削除」の3つだけ。

基本的なパターンを紹介します:

const DB_NAME = 'offline-queue';
const STORE_NAME = 'operations';

function openDB(): Promise<IDBDatabase> {
  return new Promise((resolve, reject) => {
    const request = indexedDB.open(DB_NAME, 1);
    request.onsuccess = () => resolve(request.result);
    request.onerror = () => reject(request.error);
    request.onupgradeneeded = (event) => {
      const db = (event.target as IDBOpenDBRequest).result;
      db.createObjectStore(STORE_NAME, { keyPath: 'id' });
    };
  });
}

トランザクションを開始し、操作を実行し、完了を待つ。この流れを覚えれば、あとは応用です。

ライブラリを入れると依存関係が増えますし、バンドルサイズも大きくなります。シンプルな用途なら、生 API で書いた方がコントロールしやすいと感じました。

実装:オフライン検出

オフライン状態をリアルタイムで検出する必要があります。React 18 の useSyncExternalStore が便利でした。

import { useSyncExternalStore } from 'react';

export function useOnlineStatus() {
  return useSyncExternalStore(
    (callback) => {
      window.addEventListener('online', callback);
      window.addEventListener('offline', callback);
      return () => {
        window.removeEventListener('online', callback);
        window.removeEventListener('offline', callback);
      };
    },
    () => navigator.onLine,
    () => true // SSR時はオンラインと仮定
  );
}

このフックを使えば、コンポーネント内でオンライン/オフライン状態を取得できます。状態が変わると自動的に再レンダリングされます。

実装:オンライン復帰時の自動同期

online イベントで同期をトリガーします。ポイントは2つあります。

ポイント1:初回読み込み時のチェック

ユーザーがオフライン中にアプリを閉じ、オンライン復帰後に再度開いた場合を考えてください。

この場合、online イベントは発火しません。アプリが開いた時点で既にオンラインだからです。

だから、初回読み込み時にも「キューに未同期の操作があるか」をチェックし、あれば同期を実行する必要があります。

ポイント2:同期中のロック

同期処理が重複して実行されないよう、ロックを取る必要があります。

online イベントが連続して発火したり、ユーザーが手動で「今すぐ同期」ボタンを押したりするケースがあります。同期処理が二重に走ると、同じ操作が2回送信されてしまいます。

フラグ変数を使って、同期中は新しい同期を開始しないようにしました。

ユーザーへのフィードバック

オフライン状態をユーザーに伝えることも重要です。

画面の隅に「オフラインモード」と表示するだけで、ユーザーは「今の操作は後で同期される」と理解できます。何も表示しないと「保存されたのか?」と不安になります。

また、同期に失敗した操作がある場合は、その旨を通知します。「3件の操作が同期できませんでした。確認してください」のように。

黙って失敗を飲み込むと、ユーザーは「保存されたはずなのにデータがない」という状況に陥ります。これは最悪の UX です。

Next.js での PWA 対応

開発日記に書いたライブラリ選定:

Next.js の PWA 対応は @ducanh2912/next-pwa が楽

有名な next-pwa というライブラリがあります。しかし、Next.js 16 の App Router との相性が悪いという問題がありました。

@ducanh2912/next-pwa はフォーク版で、App Router に正式対応しています。設定もシンプルで、next.config.ts に数行追加するだけで Service Worker が生成されます。

ライブラリ選定では、「Next.js のバージョン」と「App Router / Pages Router どちらを使っているか」を確認することが大切です。古い情報に惑わされないよう注意してください。

学んだこと

オフラインキューの実装で学んだのは、制限を設けることの重要性です。

「すべての操作を完璧に同期する」は理想ですが、現実的ではありません。ストレージ、競合、パフォーマンスの問題がある。

100件・7日・3回という制限は、ユーザー体験と技術的な安全性のバランスです。この数字に正解はありません。でも、根拠を持って決めることが大切です。

「なんとなく100件」ではなく、「1日10件 × 10日 = 100件」という計算がある。「なんとなく7日」ではなく、「データ競合のリスクを考慮して7日」という判断がある。

根拠があれば、後から見直すこともできます。「実際の利用データを見たら、1日20件の人もいた。制限を200件に上げよう」というように。

よくある疑問

この設計について、いくつか疑問があるかもしれません。

Q: 制限に引っかかったらデータは消えるの?

A: 古いデータから削除されます。ユーザーには「オフラインストレージがいっぱいです。古い未同期データが削除されました」と通知します。

突然消えるのではなく、警告を出すのがポイントです。

Q: 7日の制限は厳しすぎない?

A: 最初は30日にしようと思いました。でも、30日前の操作を今更同期するのは危険です。サーバー側のデータが大きく変わっている可能性が高い。

むしろ「7日以上オフラインが続くなら、一度オンラインに戻ってください」というのが正しい運用だと考えました。

Q: リトライ回数は3回で足りる?

A: ネットワークの一時的な問題なら、3回以内に成功します。3回失敗するのは、そもそも同期できないデータである可能性が高い。

回数を増やしても、成功する可能性は上がりません。むしろ、失敗し続けるデータがキューに溜まって、他の同期を遅らせるリスクがあります。

まとめ

設計判断理由
最大100件ストレージ容量と同期パフォーマンス
7日で失効データ競合のリスク軽減
3回リトライ永続的な失敗を防止
生 IndexedDBシンプルな用途にはライブラリ不要
@ducanh2912/next-pwaNext.js App Router 対応

PWA のオフライン対応は「完璧な同期」を目指すのではなく、「許容できる制限」を設計することだと学びました。

「オフラインでも使える」という機能は、ユーザーにとって大きな価値があります。でも、その実装には多くの設計判断が必要です。

この記事が、同じ課題に取り組む人の参考になれば幸いです。


関連記事

Zeronova avatar

Zeronovaゼロノバ

Product Manager / AI-Native Builder

Web/IT業界19年以上・20以上のWebサービスを担当したPdM。東証プライム上場企業の子会社代表として事業経営を経験。現在はAIを駆使して企画から実装まで完結させる個人開発を実践中。

関連プロダクト

IdeaSpool

AIが整理する思考キャプチャツール