【ITニュース解説】Offline PWA: the pitfalls of an app that never asks for the network
2026年09月17日に「Dev.to」が公開したITニュース「Offline PWA: the pitfalls of an app that never asks for the network」について初心者にもわかりやすく解説しています。
ITニュース概要
真にオフラインで動作するPWAでは、サーバー同期がないためコード更新が独特の課題となる。必要な全ファイルを正確にキャッシュし、デプロイパスの誤りによる空白画面を防ぐ必要がある。自動更新はバックグラウンドで行われるが、ユーザー操作を妨げないアイドル時にページを再読み込みし、バージョンを明示することが重要だ。
ITニュース解説
システムエンジニアを目指す皆さんにとって、Webアプリケーションの開発は興味深いテーマだろう。多くのWebアプリはインターネット接続を前提としているが、中には「ネットワークがなくても完璧に動作する」ことを目指す挑戦的なプロジェクトもある。今回は、まさにそのような「完全にオフラインで動作するPWA(Progressive Web App)」の開発で直面する課題と、その解決策について解説していく。
このアプリ「Mes mots」は、まだ言葉を話せない子供のための絵カードコミュニケーションアプリだ。開発の重要な要件は、飛行機モードのようなインターネット接続がない環境でも問題なく動作すること、数日間Wi-Fiに接続されないタブレットでも利用できることだった。これは、多くのWebアプリが考える「オフラインファースト」とは一線を画する要件である。
一般的に「オフラインファースト」と呼ばれるアプリ、例えばTrelloやGoogleドキュメント、メールクライアントなどは、基本的にはサーバーが存在する。これらのアプリは、インターネット接続がある時にデータをサーバーと同期し、オフライン時にはそのキャッシュを使ってユーザーに作業を継続させる。ネットワークが回復した時に、オフライン中の変更をサーバーに送信してデータを同期する。この「同期」のプロセスが非常に複雑で、複数のユーザーが同じデータを編集した場合の競合解決や、データの整合性を保つための高度な技術が必要となる。多くのエンジニアリングの課題が、この同期と競合解決に集中しているのだ。
しかし、「Mes mots」はそうした一般的なオフラインファーストとは異なる。このアプリにはサーバーが存在しない。つまり、同期すべきデータも、競合を解決すべき相手もいないのだ。ネットワークから「Mes mots」に届く唯一のものは、ごくまれに配信されるアプリ自身の新しいコード(プログラム)だけである。これは、ネットワークへのフォールバック(代替手段)を持つオフラインファーストではなく、「オフライン」が基本で、たまに「アップデート」が行われるという特殊なカテゴリのアプリと言える。サーバーがないため、データ同期の競合問題や、認証トークンの更新といった、一般的なオフラインファーストアプリが抱える多くの複雑な問題が最初から存在しない。その代わりに、「ユーザーが現在アプリを使っている最中に、その操作を邪魔することなく、新しいコードのアップデートをどうやって適用するか」という、より限定的だが非常に難しい問題が浮上してくる。
アプリを完全にオフラインで動作させるためには、すべての必要なファイルをユーザーのデバイスにキャッシュしておく必要がある。これを実現するのが「サービスワーカー」という技術で、PWAでは必須となる要素だ。サービスワーカーは、ネットワークとブラウザの間で動作し、アプリが必要とするファイル(HTML、CSS、JavaScript、画像、フォントなど)を賢くキャッシュする。今回のプロジェクトでは「vite-plugin-pwa」というプラグインが利用され、これは「Workbox」というライブラリを使ってサービスワーカーを生成する。最初のステップは、どのファイルをキャッシュするかをリストアップすることだ。
このキャッシュ対象のリスト、特にglobPatternsという設定には落とし穴があった。一般的なWebアプリが生成するファイルの種類は網羅されていたが、このアプリが「話す」という機能を持っていたため、各絵カードの音声ファイル(MP3)もキャッシュする必要があったのだ。もしMP3ファイルがリストに含まれていないとどうなるか。アプリは完全にインストールされ、オフラインでもエラーなく開く。絵カードも表示され、見た目にはすべて正常に動作しているように見える。しかし、絵カードをタップしても音声は再生されない。コンソールにエラーは表示されず、エラー画面も出ない。ただ、音が鳴らない静寂だけがそこにある。そして、この「音が鳴らない」という問題は、まさにインターネット接続がない飛行機モードで、親がアプリを使わせようとした最悪のタイミングで初めて気づくことになるだろう。
PWAにおけるキャッシュのバグの厄介な点は、それがアプリを「壊す」のではなく、機能を「静かに奪う」という点にある。エラーメッセージが出ないため、問題の原因特定が非常に難しい。この経験から得られる教訓は、アプリがネットワークなしで機能するために必要なものを、コードだけでなく、すべてのリソースを明示的にリストアップすることの重要性だ。
次に直面したのは、アプリがデプロイされる環境の違いによる問題だった。このアプリは、家族のタブレットではドメインのルートで公開され、公開デモ版はGitHub Pages上でサブフォルダ内に配置されていた。サービスワーカーとそのキャッシュされたアセットは、特定のベースURLにスコープが限定される。そのため、ルートドメイン用にビルドされたPWAをサブフォルダから提供すると、サービスワーカーがファイルを本来の場所よりも一つ上の階層で探しに行ってしまい、結果として「空白のページ」が表示されることになる。ここでも、デベロッパーツールで読み取れるような明確なエラーは表示されない。このようなバグは、ターミナルが使えない家族のタブレット上でリモートデバッグすることは非常に困難だ。この問題は、ビルド時に環境変数を使ってベースURLを適切に設定することで解決されたが、重要なのは「間違ったパス設定が、なぜ原因不明の空白ページにつながるのか」を理解することである。
そして、最も複雑な課題の一つが、アプリのアップデートだった。vite-plugin-pwaのregisterType: 'autoUpdate'という設定を使うと、サービスワーカーはネットワーク接続を検知すると同時に新しいバージョンのアプリをバックグラウンドでダウンロードし、自動的にインストールする。ユーザーに「アップデートがあります」といった通知を表示してクリックを促すことなく、すべてが裏側で行われる。しかし、この設定が教えてくれない重要な事実がある。それは、すでに開いているページは、完全にリロードされるまで古いJavaScriptコードで動作し続けるという点だ。
新しいサービスワーカーがページの制御を握ったことを検知するために、ブラウザはcontrollerchangeというイベントを提供する。このイベントをリッスンすることで、「新しいバージョンが利用可能になった」という状態をアプリ内で管理できる。このリスナーをどこに配置するかにも、落とし穴があった。当初、このリスナーは親の設定画面にのみ存在していたため、子供が使用するメイン画面ではアップデートの準備完了が検出されず、親が設定画面を開くまで何日も新しいバージョンが適用されないという問題が発生した。そこで、アプリ起動時にこのリスナーを常に有効にすることで、新しいサービスワーカーが有効になったことを即座に検知できるように修正された。また、アプリの初回インストール時にはまだサービスワーカーが存在しないため、controllerが存在しない場合はアップデートとはみなさない、というガードも追加されている。
アップデートが準備できたことを検知したとして、次に問題になるのは「いつそのアップデートを適用するか」だ。単純にページを即座にリロードしてしまうと、子供が言葉を話している途中で音声が途切れたり、作成途中の文章が消えたり、親が設定画面で入力中のフォーム内容が失われたりする可能性がある。画面が突然変わってしまうことは、子供にとって不快な驚きとなるだろう。
この問題を解決するためには、「アイドル状態」を定義し、アプリが安全にリロードできる瞬間を待つ必要がある。今回のアプリでは、子供用の画面が表示されていて、絵カードが音声を再生しておらず、作成中の文章も読み上げられておらず、さらに作成中の文章が空である、これら全ての条件が満たされた時を「タブレットがアイドル状態」と定義した。これらの条件がすべて満たされるまで、アプリのリロードは「辛抱強く」待機する。アプリが一日中開かれ、めったに閉じられることのない環境では、このようなアイドル状態は通常数分以内に訪れ、ユーザーはアップデートに気づくことなくスムーズに新しいバージョンに切り替わる。これは、このプロジェクトに限らず、一般的なWebアプリにも通じる重要な原則だ。ブログやダッシュボードのように、ページの状態が重要でないアプリであれば、サイレントなバックグラウンドアップデートは問題ない。しかし、フォーム入力中やメディア再生中など、ページの状態がユーザーにとって価値を持つ瞬間においては、単純なタイマーではなく、明確な「安全なポイント」を見つけてアップデートを適用する必要がある。
最後に、コードとしては小さな修正だが、実運用上非常に重要なのが、現在動作しているアプリのバージョンを画面に表示することだった。開発者はサービスワーカーのキャッシュ情報を確認するためにデベロッパーツールを開くが、一般のユーザー、特に子供の親はそのようなツールを使うことはない。親が「このアプリは最新バージョンになっているか?」という疑問を持った時、画面を見ただけでビルド日などのバージョン情報が確認できるようにすることで、デバッグの手間を省き、安心感を提供できる。
Web開発では、これまでネットワークの不安定さに対する対策を学んできた。しかし、ネットワークにまったく依存しないアプリを構築するということは、別の種類の複雑さを生み出す。それは「アップデートが静かに到着し、ユーザーが現在利用している足元を安全に入れ替える、その正確な瞬間を見極める」という、これまであまり意識してこなかった領域に複雑性が移動することを意味する。ユーザー体験を損なわずに、裏側でアプリを最新の状態に保つ工夫は、オフラインPWA開発の醍醐味であり、大きな挑戦であると言えるだろう。