PHPで作ったサービスとPWAがバッティングした話。sw.jsを改造するの巻

2023.09.30

題名の通りPHPで作ったサービスをXserverにアップしてみたところ、既存のプログラムとバッティングが起きていた。

そもそもはログインしたらユーザー情報(ユーザー名など)がルートページに表示される仕組みになっていたが、それらが表示されなかった。

もっと具体的にいうと、スーパーリロードをすればユーザー情報が表示されるが、F5を押すとまたログイン前画面に戻ってしまうのだ。

原因はPWA化に伴うsw.jsの書き方

原因を探る過程でXserverのキャッシュを削除してみたり、アクセレーターをオフにしてみたり、PHP側でキャッシュ設定をしてみたが、効果が無かった。

しかし、一つひとつ検証していく中で、どうやらPWA化に伴うsw.jsの設定とバッティングしていることに気付いた。


// ServiceWorker処理:https://developers.google.com/web/fundamentals/primers/service-workers/?hl=ja

// キャッシュ名とキャッシュファイルの指定
var CACHE_NAME = 'pwa-sample-caches-v2';
var urlsToCache = [
	"/",
    "index.php",
	"api/library.php",
	"api/library_data.php",
	"common/session.php",
	"function/repository.php",
	"login_form.php",
	"mail/contact.php",
	"mail/mail.php",
	"profile.php",
	"record.php",
	"register_finish.php",
	"register_form.php",
	"web.php",
	"image/icon-192.png"
];
//document.write("<script type='text/javascript' src='list.js'></script>");

// インストール処理
self.addEventListener('install', function(event) {
	event.waitUntil(
		caches
			.open(CACHE_NAME)
			.then(function(cache) {
				return cache.addAll(urlsToCache);
			})
	);
});

// リソースフェッチ時のキャッシュロード処理
self.addEventListener('fetch', function(event) {
	event.respondWith(
		caches
			.match(event.request)
			.then(function(response) {
				return response ? response : fetch(event.request);
			})
	);
});

PWAとは端末にサイトのキャッシュをためておき、オフラインでも閲覧可能にする機能があり、上記はまさにそれに該当する。

つまり、ログイン後もログイン前のキャッシュを表示させていたと推測出来る。

ちなみに、PHPのサービス(session)とバッティングしていた原因としては主に下記の通りだ。

①古いService Workerの問題

→新しいService Workerが登録されても、古いService Workerがアクティブのままだった

②キャッシュの累積問題

→古いキャッシュが削除されずに累積されていくと、不要なデータが増え続ける問題があった。これを解決するために、新しいService Workerがアクティブになる際に古いキャッシュを削除する必要がある。

③ネットワークとキャッシュの利用

→以前の戦略では、まずキャッシュを利用し、それがない場合にネットワークを利用するという方法だったが、まずネットワークを試み、それが利用できない場合のみキャッシュを使用するように変更する必要がある。

解決方法

具体的な解決方法は下記の通りだ。

// キャッシュ名とキャッシュファイルの指定
const CACHE_NAME = 'pwa-sample-caches-v3'; // キャッシュ名を変更して新しいキャッシュを作成
const urlsToCache = [
    "/",
    "index.php",
    "image/icon-192.png"
    // 動的に変わるコンテンツやAPIのレスポンスはキャッシュから除外
];

// インストール処理
self.addEventListener('install', function(event) {
    event.waitUntil(
        caches.open(CACHE_NAME)
            .then(function(cache) {
                return cache.addAll(urlsToCache);
            })
    );
    self.skipWaiting(); // 新しいService Workerをすぐにアクティブにする
});

// アクティブ時の古いキャッシュの削除
self.addEventListener('activate', function(event) {
    event.waitUntil(
        caches.keys().then(function(cacheNames) {
            return Promise.all(
                cacheNames.map(function(cacheName) {
                    if (cacheName !== CACHE_NAME) {
                        return caches.delete(cacheName);
                    }
                })
            );
        })
    );
});

// リソースフェッチ時のキャッシュロード処理
self.addEventListener('fetch', function(event) {
    event.respondWith(
        // Network First, Cache Fallbackの戦略
        fetch(event.request).catch(function() {
            return caches.match(event.request);
        })
    );
});

動的なコンテンツやAPIのレスポンスを除外

先ほどは全てのページをキャッシュに溜めていたが、動的なコンテンツやAPIのレスポンスは、頻繁に変更される可能性がある。

キャッシュに保存してしまうと、ユーザーが古いデータを見ることになる可能性が高まり、先ほどのようにログインしているのにログインしていない画面がでる不具合につながる。

なので。

activate イベント内での処理が不足

上記の①では、新しいService Workerがアクティブになったときに古いキャッシュを削除する処理がないので、、新しいService Workerがインストールされた後でアクティブになるように変更する。

これにより、古いキャッシュが残り続け、更新後のコンテンツが正しく表示されなかったようなので、問題は大きく解決する。

なので今回はindex.phpとPWAに必要なアイコンのみキャッシュする設定に変更した。

Network First, Cache Fallback

上記の sw.js では、キャッシュを先に試み、キャッシュが存在しない場合のみネットワークを使用する戦略が採用されていた。

これにより、キャッシュが存在する場合、常にそのため込んだキャッシュのコンテンツが表示され、新しいコンテンツが表示されない可能性があったみたいだ。

そこで、まずネットワークを試み、ネットワークが利用できない場合のみキャッシュを使用するように変更しました。

まとめ

これらの変更により、サイトの更新が正しく反映されるようになり、オフライン時やネットワーク接続が不安定な場合でもキャッシュを利用してサイトが表示されるようになった。

PHPのセッションとバッティングしていた件も解消できたので、これを見ている人も動的コンテンツのPWA化は慎重にやってもらいたい。

PIC UP