코드를 고쳤다. 배포했다. 모바일에서 확인했다. 안 바뀌어 있다. 캐시를 비웠다. 안 바뀌었다. 앱을 삭제하고 재설치했다. 그제야 바뀌었다. Service Worker가 범인이었다.
SW는 왜 업데이트를 막는가
Service Worker는 브라우저와 서버 사이에 있는 프록시다. 모든 fetch 요청을 가로채고, 캐시된 응답을 돌려줄 수 있다. 오프라인 지원에는 좋지만, 업데이트에는 적이다.
내 SW는 install 이벤트에서 주요 파일을 미리 캐시했다.
const CACHE = 'enhance-v5';
const OFFLINE = [
'/enhance/',
'/enhance/index.html',
'/lib/shared-wallet.js', // 여기가 문제
'/lib/multiplayer.js',
'/lib/multiplayer-ui.js',
];/lib/ 파일들을 precache에 넣었다. 그러니까 새 버전을 배포해도 이전 SW가 캐시된 옵날 파일을 계속 서빙했다.
캐시버스팅만으로는 부족하다
shared-wallet.js?v=20260309c를 ?v=20260310으로 바꿀다. 캐시버스팅이다. 그런데 SW는 URL의 쿼리 파라미터를 무시하고 경로만 본다. /lib/shared-wallet.js는 이미 캐시에 있으니까 그걸 돌려준다.
해결책:
/lib/파일을 precache에서 제거- fetch 이벤트에서
/lib/경로는 항상 네트워크우선
self.addEventListener('fetch', e => {
const url = new URL(e.request.url);
if (url.pathname.startsWith('/lib/')) {
e.respondWith(fetch(e.request));
return;
}
// 나머지는 network-first with cache fallback
e.respondWith(
fetch(e.request).catch(() => caches.match(e.request))
);
});이제 /lib/ 파일은 항상 서버에서 받아온다. 캐시버스팅만 올리면 즉시 반영된다. 오프라인일 때만 실패하는데, 게임 중에 오프라인되는 것보단 최신 코드를 못 받는 게 더 나쁘다.
배운 것
- SW 버전을 올리면
skipWaiting+clients.claim으로 즉시 반영된다. 단, 사용자가 앱을 다시 열어야 한다 - PWA로 설치된 앱은 SW가 더 단단히 붙잡는다. 웹브라우저보다 앱 삭제/재설치가 확실하다
- 모든 패치에 SW 버전을 올려라.
enhance-v5→enhance-v12. 하루에 7번 올렸다
다음 화: Google OAuth 통합 — 버튼 하나로 로그인하기
플레이: game.cocy.io/enhance