서비스 워커의 실제 활용 사례를 살펴보며, 많은 문제를 더 단순한 HTTP 캐싱, 서버 측 처리, 정적 자산 보존 전략으로 해결할 수 있다는 점을 설명합니다.

Neciu가 최근 service workers의 몇 가지 흥미로운 활용 사례를 분석했습니다. 저는 특히 이 대목에서 정말 ‘내 이야기 같다’고 느꼈습니다:
제 설문조사에서 “2019년에 한 번 써보고 제거했다”라고 답한 두 사람은 세부 사항만 다를 뿐 같은 이야기를 했습니다. 잘못된 캐시 전략을 가진 서비스 워커가 사용자에게 오래된 앱을 제공했고, 수정하려면 킬스위치 워커를 배포한 뒤 클라이언트가 그것을 받아갈 때까지 며칠을 기다려야 했습니다. 망가진 워커가 업데이트 확인 시점까지 제어하고 있었기 때문입니다.
서비스 워커가 처음 등장했을 때 저는 초기에 도입한 편이었고, 곧 비슷한 상황에서 스스로 발등을 찍었습니다.
이 글의 몇 가지 예시를 함께 뜯어봅시다(아직 읽지 않았다면, 먼저 꼭 읽어보세요!).
이 글에서 가장 설득력 있는 예시는 Slack의 사례입니다. 전체 자산 세트를 캐시하고 Redux 상태를 다시 주입해, 단 하나의 네트워크 요청도 완료되기 전에 UI를 렌더링할 수 있게 하는 방식입니다.
다만 자산 부분은 조금 과장된 느낌이 있습니다:
그들은 부팅 사이에 그 자산 세트에서 거의 아무것도 바뀌지 않는다는 점을 확인했습니다.
화요일 아침에 Slack을 여는 사용자는 월요일 아침에 내려받았던 것과 같은 JavaScript를 내려받습니다.
이 정도는 HTTP 캐시만으로도 충분히 완화할 수 있고, 훨씬 단순합니다. 변경되지 않은 자산이라면 콘텐츠 해싱과 Cache-Control: public, max-age=31536000, immutable을 통해 캐시에서 직접 제공되도록 할 수 있습니다.
다만 이것만으로는 네트워크 없는 부팅을 제공하지는 못합니다. 여전히 HTML과 필요한 선행 데이터를 가져와야 합니다. 저는 이것이 더 본질적으로는 ‘오프라인 지원이 필요한가?’라는 질문이라고 봅니다. Slack이라면 그렇겠지만, 많은 앱에는 아마 아닐 것입니다.
같은 자산의 반복 다운로드를 피하고 싶은 것이 전부라면, 그 자산에 해시를 붙이고 기본 캐싱을 활용하면 됩니다.
이건 흥미로운 사례입니다. Vercel 같은 일부 벤더는 ‘skew protection’을 제공하지만, 우리 대부분은 예전에 이런 문제를 겪어봤을 것입니다. 클라이언트에 남아 있는 오래된 번들 때문에, 참조된 자산이 더 이상 존재하지 않을 때 404가 발생하는 문제입니다.
배포를 자주 할수록 이 문제는 더 자주 발생합니다(진정한 CI를 실천한다면 하루에 수백 번 배포할 수도 있습니다).
여기서 Neciu의 해법은 서비스 워커를 사용해 앱을 로컬에 캐시하는 것입니다. 하지만 이는 결국 백그라운드에서 모든 것 을 캐시한다는 뜻입니다:
{
"version": "2026.06.04-1412",
// 이게 어디까지 가야 할까?
"assets": ["/assets/index-c91d44.js", "/assets/Settings-c91d44.js"]
}
제 생각에는 이것은 라우트 분할/코드 분할의 취지를 무색하게 만듭니다. 물론 초기 렌더링은 더 빨라지겠지만, 한 번의 무효화가 발생할 때마다 클라이언트가 앱 전체 를 다시 가져와야 한다는 뜻이기도 합니다. 제가 작업해 온 대부분의 앱에서는 이것이 매우 크고, 대부분 낭비되는 페이로드로 이어졌을 것입니다. 사용자가 어떤 컴포넌트나 페이지를 방문할지 확실하게 예측할 수 없으므로, 이론상으로는 전체 매니페스트의 내용을 모두 내려받아야 합니다.
그 대신 정적 자산을 그냥 남겨두면 어떨까요(유예 기간 동안)? 곧바로 삭제하는 대신 버킷 안에서 계속 살아 있게 두는 것입니다. 콘텐츠 해시가 포함된 파일명을 사용하면 배포가 기존 파일을 덮어쓰지 않습니다. Settings-a3f8b2.js와 Settings-c91d44.js는 공존할 수 있습니다.
서비스 워커는 백그라운드에서 무기한 실행되지 않기 때문에, 핵심 재요청 로직은 어차피 메인 앱 안에 있어야 합니다:
대신 페이지가 폴링을 주도하며, 일정 간격과
visibilitychange시점에CHECK_VERSION을 게시합니다. 그래서 주말 내내 백그라운드에 있다가 다시 돌아온 탭은 즉시 확인합니다.
따라서 이것 역시 서비스 워커가 꼭 필요한 것은 아닙니다.
이건 멋지지만, 클라이언트에 둘 로직은 아닌 것 같습니다. 글에서 언급된 버그는 사실 이 로직이 클라이언트 쪽에 있다는 점의 증상 입니다:
비디오 플레이어는 마운트되는 순간부터 가져오기를 시작하므로, 같은 페이지의 워커가 제어권을 잡기 전에 요청이 시작됩니다. 그래서 그들은 인덱스 페이지에서 워커를 등록하고, 거기서 플레이어 페이지로 링크를 넘겨야 했습니다.
대신 이 재작성을 서버 측으로 옮기면 더 견고하고 테스트하기도 쉬워집니다. 재작성해야 하는 것은 매니페스트(텍스트 파일)뿐이므로, 거대한 비디오가 추가 인프라 계층을 통과하게 되는 문제도 없습니다.
실제로 글에서도 이렇게 짚고 있습니다:
…Cloudflare Workers 같은 엣지 런타임이 동일한 fetch 이벤트 API를 구현하기 때문에, 그들은 스티칭 워커를 수정 없이 Cloudflare에 배포했고 동작하는 URL을 얻었습니다.
좋은 예시이긴 하지만, 서비스 워커 버전이 실제로는 폴백이라는 점은 짚고 넘어갈 만합니다:
Partytown은 브라우저에서 Atomics와 SharedArrayBuffer를 사용할 수 있으면 그것들을 사용합니다.
안타깝게도 SharedArrayBuffer는 교차 출처 격리 환경에서만 동작하고, 그런 헤더는 서드파티 임베드를 깨뜨리는 경우가 많습니다. 그래서 실제로는 서비스 워커 폴백이 생각보다 더 자주 사용되지만, 여전히 탈출구에 더 가깝습니다.
이건 무엇을 만들고 있는지에 따라 다르지만, 서버 주도 렌더링 전략과 데이터 로딩으로 이동하면서 아마 대신 setupServer를 사용하고 있을 가능성이 큽니다(Node 내부를 패치하는 방식입니다).
라이브러리 이름과 달리, 전통적인 SPA만이 문자 그대로의 서비스 워커를 사용하게 될 것입니다.
서비스 워커로 할 수 있는 멋진 일은 정말 많습니다. 그리고 실제로 서비스 워커만 할 수 있는 일도 몇 가지 있습니다. 오프라인 지원, 푸시 알림, 백그라운드 동기화는 현실적인 대안이 없습니다.
하지만 그런 경우를 제외하면, 저는 아직 서비스 워커가 진정으로 최선의 해법이었던 문제를 거의 만나지 못했습니다.
좋은 예시가 있나요? 알려주세요. 다시 한 번 진지하게 살펴볼 핑계를 찾고 있었습니다.