PWAで画面遷移できない原因は?Service Workerとキャッシュの確認方法
PWA / Service Worker / キャッシュ / 個人開発
PWAでリンクやボタンを押しても、次のページが開かない。そんなときは、Service Workerを経由した場合だけ失敗するのかを先に確かめると、調べる場所を絞れます。
私の飲み会カウンター「何杯飲んだっけ?」でも、記録を保存した後に履歴画面へ進めなくなりました。履歴ページのHTMLはサーバーにあり、HTTPで取得すれば200が返ります。それでも、Service Workerが有効なChromeでは ERR_FAILED になっていました。
この記事では、PCのChrome・Edgeの開発者ツールで確認する順番と、このアプリで見つかった原因をまとめます。
まず、どの操作で止まるかを確認する
「画面遷移できない」には、リンク先の間違い、JavaScriptのエラー、Service Workerが返すレスポンスの問題などが含まれます。最初に、失敗する操作とURLを確認します。
| 確認すること | 次に調べる場所 |
|---|---|
| ボタンを押しても、遷移先への要求が出ない | クリック処理とConsoleのエラー |
| 遷移先が404になる | リンクの相対パスと公開ファイルの配置 |
| SWを迂回したときだけ開ける | SWのfetch処理とCache Storage |
| ページは開くが、別ウィンドウやアドレスバーが出る | 遷移先URLとmanifestのscope |
「何杯飲んだっけ?」では、保存後に次の処理で履歴へ移動していました。
location.href = 'history.html';
本番の配置は /drink-session-counter/ 配下なので、移動先は /drink-session-counter/history.html です。ここで、開発環境のルート / に置いていたときと同じつもりで /history.html と書くと、サイト直下を開いてしまいます。
リンクの文字列だけでなく、実際にどのURLへ移動しようとしているかを見ます。
Service Workerを迂回して同じ操作を試す
Chrome・Edgeでは、開発者ツールからService Workerを一時的に迂回できます。
- 対象のページで
F12を押し、開発者ツールを開く Application→Service workersを開くBypass for networkにチェックを入れる- ページを再読み込みして、失敗したリンクやボタンをもう一度押す
- 比較が終わったらチェックを外す
この方法はMicrosoftのPWAデバッグ資料にも載っています。
迂回すると開けるなら、SWやSWから返されるキャッシュが原因候補になります。変わらなければ、URLやページ側のJavaScriptも引き続き調べます。
最初からサイトのデータを全部削除する必要はありません。 Clear site data などで保存領域まで消すと、localStorage やIndexedDBに置いたアプリの記録も失うことがあります。このカウンターも記録を端末内に保存しているので、まずデータを残したまま比較する方法が合っています。
HTTP 200の前にリダイレクトがないかを見る
次に、Networkでページの要求を確認します。画面が切り替わってもログを残せるよう、Preserve log を有効にしてから操作します。
私のアプリでは、本番で次のリダイレクトが発生していました。
/drink-session-counter/history.html
↓ 308リダイレクト
/drink-session-counter/history
↓ 200
履歴ページのHTML
Cloudflare Pagesは、HTMLのURLを拡張子なしの形へリダイレクトします。たとえば /contact.html は /contact へ移ります。Cloudflareの配信仕様で説明されている挙動です。
curlなどでリダイレクトを追えば、最終的なHTMLは取得できます。ただし、curlはブラウザのService Workerを通りません。
今回も、サーバーにファイルがあることと、SW経由で画面が開けることは別でした。「200だからページ側は問題ない」と判断すると、間に入っているSWの処理を見落とします。
キャッシュから何を返しているかを確認する
修正前のSWは、すべての要求に対してキャッシュを優先していました。
self.addEventListener('fetch', event => {
event.respondWith(
caches.match(event.request)
.then(cached => cached || fetch(event.request))
);
});
キャッシュがあれば返し、なければネットワークから取得する処理です。短く書けますが、ページ遷移と画像の取得を区別していません。
さらに、インストール時のキャッシュ対象には history.html が入っていました。リダイレクトを追って取得したレスポンスには、その取得履歴が残ります。最終的なステータスが200でも、そのレスポンスをナビゲーションへそのまま返すと、要求の条件によってはブラウザがエラーにします。
自分のアプリで同じ点を調べる場合は、Application → Cache storage で、遷移先のHTMLがどのURLで保存されているかを確認します。アプリごとにキャッシュを分けているなら、該当するキャッシュを選びます。
SWのfetchハンドラで、respondWith() に渡す直前の値をログに出す方法もあります。request と response は、その処理で使っている変数に合わせます。
console.log({
requestURL: request.url,
mode: request.mode,
redirect: request.redirect,
responseURL: response.url,
status: response.status,
redirected: response.redirected
});
redirected: true だけで不具合と決まるわけではありません。ページ遷移の要求に対して、キャッシュとネットワークのどちらから、どのレスポンスを返しているかを照らし合わせます。
履歴画面の不具合で変更したところ
今回は、ページ遷移の要求を request.mode === 'navigate' で分け、ネットワークを優先するようにしました。画像などは従来のキャッシュ優先を残しています。
取得したレスポンスがリダイレクト済みなら、本文・ステータス・ヘッダーから新しい Response を作る処理も加えました。次のコードは、その部分の抜粋です。
const response = await fetch(request);
if (!response.redirected) return response;
return new Response(response.body, {
status: response.status,
statusText: response.statusText,
headers: response.headers
});
元のレスポンスに付いたリダイレクト履歴を、新しいレスポンスへ引き継がないための処理です。キャッシュを優先して即座に返す経路も変えたうえで、2026年9月7日に本番で保存後の履歴画面が開くことを確認しました。
ここで確認したのはオンラインでの復旧です。通信失敗時に戻るキャッシュにも同じ履歴が残っていれば、オフラインでは別途対応が必要です。
修正前後のfetchハンドラ全体と、レスポンスを返せる条件については、Zennに詳しくまとめています。
👉 curlでは200なのに、ブラウザでは開けない。PWAの画面遷移を壊したService Worker
修正後はSWを有効に戻して確認する
Bypass for network を入れたままでは、修正したSWを確認できません。チェックを外し、新しいSWが有効になったことを確かめてから操作します。
ページ側のConsoleでは、現在そのページを制御しているSWを確認できます。
navigator.serviceWorker.controller?.scriptURL
URLが返れば、そのページはSWに制御されています。undefined の場合は、初回アクセスでまだ制御されていないなどの可能性があります。
私のアプリで確認したかったのは、履歴ページ単体の表示だけではありません。「終了して保存」を押し、履歴へ進み、保存した内容を読めるところまでです。
PWAの確認では、初めて開く状態に加えて、SWが登録されている状態でも同じ操作を試す。この一手間が必要でした。
今回の修正対象は、飲み会中に杯数を数え、終了後に記録を見返す小さなツールです。画面や使い方は「何杯飲んだっけ?」の紹介ページに載せています。