JavaScript | Web API:通信・ネットワーク系 - オフライン対応の基本

JavaScript JavaScript
スポンサーリンク

まず「オフライン対応って何を指しているのか」をはっきりさせる

オフライン対応と聞くと、
「ネットが切れても全部普通に動く魔法」みたいに思いがちですが、
現実はもう少し地に足がついた話です。

本質は、
「ネットワークが不安定でも、ユーザー体験をできるだけ壊さないようにする設計」
です。

完全オフラインでフル機能を動かすのはかなり難しいですが、

最低限の画面は開ける
最後に取得したデータだけでも見せる
「今はオフラインだよ」と分かるメッセージを出す

こういう小さな工夫の積み重ねが「オフライン対応」です。
その中心にあるのが、Service Worker とキャッシュの考え方です。


オフライン対応の基本パターンをざっくり分解する

1. 「アプリの殻」をオフラインでも開けるようにする

まず一番最初にやるべきは、
「アプリの土台(HTML / CSS / JS)をオフラインでも読み込めるようにする」 ことです。

ここで登場するのが Service Worker と Cache Storage です。

流れとしてはこうです。

最初のオンライン時に、
Service Worker がインストールされる
そのタイミングで、index.htmlmain.js などをキャッシュに保存する

次回以降、
ユーザーがその URL を開く
ネットワークがオフラインでも、Service Worker がキャッシュからファイルを返す

これだけで、

「とりあえずアプリの画面までは開ける」
「真っ白なエラー画面ではなく、自分の UI を見せられる」

という状態になります。

2. データをどうするかを決める(読み取り)

次の段階は、
「サーバーから取ってくるデータをどう扱うか」 です。

例えば、ToDo アプリを考えます。

オンライン時に API から ToDo リストを取得
その結果を IndexedDB や Cache Storage に保存
オフライン時は、保存しておいたデータを表示

こうすると、

「最新ではないかもしれないけど、最後に見た ToDo は表示できる」

という状態になります。

ここで大事なのは、

「常に最新であること」よりも
「何も見えないよりマシな状態を作ること」

を優先する、という発想です。

3. データの書き込みをどう扱うか(同期の問題)

さらに一歩進むと、
「オフライン中にユーザーが行った操作をどう扱うか」
という問題が出てきます。

例えば、オフライン中に ToDo を追加したらどうするか。

すぐにはサーバーに送れないので、
一旦ローカル(IndexedDB など)に「未同期の変更」として保存しておく
オンラインに戻ったタイミングで、まとめてサーバーに送る

こういう仕組みを「同期(sync)」と呼びます。

ここまで来ると、
設計も実装も一気に難しくなりますが、
「読み取りだけオフライン対応」よりも、
かなりリッチな体験になります。


一番最初にやるべき「オフライン対応の最小セット」

Service Worker で「オフライン用のフォールバックページ」を用意する

いきなり全部をオフライン対応する必要はありません。
まずは、「オフライン時に見せる専用ページ」を用意する のが良いスタートです。

例えば、/offline.html を用意しておきます。

<!-- offline.html のイメージ -->
<!doctype html>
<html lang="ja">
  <head>
    <meta charset="UTF-8" />
    <title>オフラインです</title>
  </head>
  <body>
    <h1>オフラインです</h1>
    <p>ネットワークに接続されていないようです。</p>
    <p>接続が回復したら、ページを再読み込みしてください。</p>
  </body>
</html>

Service Worker 側では、
インストール時にこのページをキャッシュしておきます。

const CACHE_NAME = "offline-demo-v1";
const OFFLINE_URL = "/offline.html";

self.addEventListener("install", (event) => {
  event.waitUntil(
    caches.open(CACHE_NAME).then((cache) => cache.addAll([OFFLINE_URL]))
  );
});
JavaScript

そして、fetch イベントでこうします。

self.addEventListener("fetch", (event) => {
  event.respondWith(
    fetch(event.request).catch(async () => {
      const cache = await caches.open(CACHE_NAME);
      return cache.match(OFFLINE_URL);
    })
  );
});
JavaScript

これで、

オンライン時
普通にネットワークからレスポンスを返す

オフライン時(fetch が失敗したとき)
offline.html を返す

という挙動になります。

真っ白な「ネットワークエラー」ではなく、
自分で用意した「オフライン画面」を見せられるだけでも、
ユーザー体験はかなりマシになります。


「オンラインかオフラインか」を UI に反映する

navigator.onLine と online/offline イベント

JavaScript からは、
「今オンラインかどうか」 をざっくり知ることができます。

console.log(navigator.onLine); // true or false
JavaScript

また、状態が変わったときのイベントもあります。

window.addEventListener("online", () => {
  console.log("オンラインになりました");
});

window.addEventListener("offline", () => {
  console.log("オフラインになりました");
});
JavaScript

これを使って、

画面の上に「オフラインです」とバナーを出す
オンラインに戻ったら「再読み込みしますか?」と聞く

といった UI を作れます。

ここで大事なのは、
「状態を隠さない」 ことです。

ユーザーにとって一番つらいのは、
「押しても反応しない」「なぜ動かないのか分からない」状態です。

「今はオフラインだから、この操作は保留中です」
「オンラインに戻ったら送信します」

といったメッセージを出すだけでも、
体験はかなり変わります。


オフライン対応で意識すべき設計の考え方

「何がオフラインでできて、何ができないか」を決める

全部をオフライン対応しようとすると、
ほぼ確実に挫折します。

代わりに、こう考えます。

このアプリで、
オフラインでも最低限できてほしいことは何か
逆に、オンライン前提で割り切っていい機能は何か

例えば、ニュースアプリなら、

最後に読んだ記事一覧はオフラインでも見たい
でも、新着記事の取得はオンライン前提でいい

ToDo アプリなら、

既存の ToDo の閲覧・チェックはオフラインでもしたい
新規追加は「オフライン中はローカルに保存して、あとで同期」でもいい

こうやって「優先度」をつけていくと、
どこから手を付けるべきかが見えてきます。

「失敗したときの顔」を必ず用意する

オフライン対応の本質は、
「失敗したときにどう振る舞うかを設計すること」 です。

ネットワークエラー
API エラー
タイムアウト

こういった「うまくいかなかったとき」に、

何も表示しない
コンソールにだけエラーが出ている

ではなく、

ユーザーに分かる形でメッセージを出す
可能なら、最後に成功したときのデータを見せる
再試行のボタンを用意する

といった「顔」を用意しておくことが、
オフライン対応の第一歩です。


初心者としてオフライン対応で本当に掴んでほしいこと

オフライン対応は「全部オフラインで動く魔法」ではなく、「不安定なネットでも壊れない設計」
最初の一歩は「アプリの殻(HTML/CSS/JS)を Service Worker でキャッシュして、オフラインでも開けるようにする」
次の段階で「最後に取得したデータをローカルに保存して、オフライン時に見せる」
さらに進むと「オフライン中の操作をローカルに溜めて、オンライン復帰時に同期する」世界になる
navigator.onLine や online/offline イベントで、状態を UI に反映するのも大事な要素
一番大事なのは、「失敗したときの顔(オフライン画面・エラーメッセージ)をちゃんと用意する」こと

もしやってみるなら、
今ある小さなアプリに「offline.html + 超シンプルな Service Worker」を足してみてください。
それだけで、「ネットが切れても自分の画面が出る」という感覚を味わえるはずです。
そこから先は、「どこまでオフラインで頑張るか」を、あなたのアプリに合わせて少しずつ広げていけばいいです。

タイトルとURLをコピーしました