
MSW나 PWA를 사용할 때마다 ‘Service Worker’라는 말을 봤지만, 그 정체를 깊이 생각해 본 적은 없었습니다.
그러던 중 PWA 개발 과정에서 캐싱 문제를 겪으면서 “도대체 Service Worker가 뭐길래 서비스 전체에 영향을 주지?”라는 의문이 생겼습니다. 그저 백그라운드에서 도는 스크립트라고만 생각했는데, 실제로는 서비스 전반의 동작 방식까지 바꿀 수 있는 존재였던 것입니다.
그래서 이번 글에서는 Service Worker가 무엇인지, 어떤 원리로 동작하는지 차근차근 살펴보려 합니다.
브라우저의 페이지와 네트워크 사이에서 요청을 가로채고 처리하는 독립 실행 환경(자바스크립트 파일).
Service Worker는 페이지의 JS와는 별도로 워커 컨텍스트에서 동작하며, 브라우저가 이벤트를 트리거할 때 실행됩니다. 이를 통해 네트워크 요청을 가로채서 원하는 응답을 반환하거나, 캐시를 이용해 오프라인 상태에서도 콘텐츠를 제공할 수 있습니다. PWA의 핵심 기술로서 푸시 알림과 백그라운드 동기화 같은 기능 구현에도 사용됩니다.
install, activate, fetch, push 등 이벤트로 동작.localhost는 예외)⚠️ Service Worker는 페이지와 네트워크 사이에서 요청을 가로채고 조작할 수 있기 때문에, 악의적인 중간자(Man-in-the-Middle)나 변조된 스크립트가 끼어들면 서비스 전체에 치명적인 영향을 줄 수 있다. 그래서 브라우저는 Service Worker를 HTTPS 환경에서만 동작하며, 개발 시 localhost는 예외다. 개발자는 서드파티 스크립트 검증과 안전한 배포 절차를 반드시 지켜야 한다.
Service Worker는 단순한 JS 파일이 아니라, 브라우저가 별도로 인식하고 관리해야 하는 워커입니다. 따라서 일반 스크립트처럼 <script>로 불러오는 것이 아니라, 명시적으로 등록(register) 해야 브라우저가 이를 감지하고 관리할 수 있습니다.
Service Worker API를 통해서 브라우저에 Service Worker를 등록 가능합니다.
serviceWorker.js
self.addEventListener('install', function () {
self.skipWaiting();
});
self.addEventListener('activate', function (event) {
event.waitUntil(self.clients.claim());
});
self는 ServiceWorkerGlobalScope를 가리키며, Service Worker의 전역 실행 환경입니다.window와는 별도의 독립 환경에서 동작하므로, 생명주기(install, activate)나 네트워크 이벤트(fetch, push 등)를 처리할 때는 self에 이벤트를 등록해야 합니다.const registerServiceWorker = async () => {
if ("serviceWorker" in navigator) {
try {
const registration = await navigator.serviceWorker.register("/serviceWorker.js");
if (registration.installing) {
console.log("Service worker installing");
} else if (registration.waiting) {
console.log("Service worker installed");
} else if (registration.active) {
console.log("Service worker active");
}
} catch (error) {
console.error(`Registration failed with ${error}`);
}
}
};
// …
registerServiceWorker();
installing, waiting, active 상태를 확인할 수 있습니다.if (isProdunction) {
registerPwaServiceWorker()
}
if (isDevelopment) {
const { worker } = await import('./mocks/browser');
worker.start()
}
msw(Mock Service Worker)처럼 Service Worker를 활용하는 라이브러리를 함께 쓴다면, 환경별로 분기하여 충돌을 방지하는 것이 좋습니다.💡 TIP: 등록된 Service Worker는 브라우저 개발자 도구에서 확인할 수 있습니다.
- Application 탭 → Service Workers: 등록 상태(
installing,waiting,activated)와 업데이트/중지 버튼 확인 가능- Sources 탭 → serviceWorker.js: 실제 등록된 스크립트를 디버깅 가능
- chrome://inspect/#service-workers
- 크롬에서 등록된 전체 Service Worker 목록을 확인할 수 있음
- 다른 도메인에 등록된 워커까지 한 번에 점검 가능




브라우저가 Service Worker 파일을 다운로드하고 파싱한 뒤, 설치 중 상태가 됩니다.
install 이벤트에서 event.waitUntil()을 사용하면 특정 비동기 작업(예: 캐시 저장)이 끝날 때까지 설치 과정을 지연시킬 수 있습니다.설치가 완료되면 설치됨 상태가 됩니다.
👉 새 버전의 Service Worker가 자동으로 바로 교체되지 않고, 기존 워커가 종료될 때까지 대기하는 이유는 사용자 경험을 보호하기 위함입니다.
설치된 워커가 앱을 제어하기 직전 상태입니다.
activate 이벤트가 발생합니다.event.waitUntil()을 사용해 캐시 정리나 마이그레이션 같은 작업을 완료할 때까지 활성화를 지연시킬 수 있습니다.Service Worker가 앱을 제어할 준비가 끝난 상태입니다.
fetch 이벤트를 처리하거나, push 이벤트를 받아 푸시 알림을 보낼 수 있습니다.Service Worker가 더 이상 쓰이지 않게 된 상태입니다.
ServiceWorkerGlobalScope에는 다양한 이벤트가 정의되어 있습니다. 그중 자주 쓰이는 이벤트를 정리하면 다음과 같습니다.
installself.addEventListener('install', function (event) {
// 기존 활성화된 워커가 있어도 곧바로 새 워커를 활성화
self.skipWaiting();
// 캐시에 필요한 리소스를 미리 저장 가능
event.waitUntil(
caches
.open("v1")
.then((cache) =>
cache.addAll([
"/",
"/index.html",
"/style.css",
"/app.js",
"/image-list.js",
"/star-wars-logo.jpg",
"/gallery/",
"/gallery/bountyHunters.jpg",
"/gallery/myLittleVader.jpg",
"/gallery/snowTroopers.jpg",
]),
),
);
});
activateself.addEventListener("activate", (event) => {
const cacheAllowlist = ["v2"];
event.waitUntil(
caches.keys().then((cacheNames) =>
Promise.all(
cacheNames.map((cacheName) => {
if (!cacheAllowlist.includes(cacheName)) {
return caches.delete(cacheName);
}
return undefined;
}),
),
),
);
});
fetchfetch() 메서드가 호출될 경우 발생self.addEventListener("fetch", (event) => {
const { method, url } = event.request;
// 특정 요청 가로채기
if (method === "GET" && url.endsWith("/hello")) {
event.respondWith(
new Response(
JSON.stringify({ message: "Hello from Service Worker!" }),
{ headers: { "Content-Type": "application/json" } }
)
);
}
});
messagepostMessage()를 사용해 Service Worker로 데이터를 보낼 때 발생합니다.// serviceWorker.js
self.addEventListener("message", (event) => {
console.log("Message from page:", event.data);
// 받은 메시지에 따라 응답을 보낼 수도 있음
event.source.postMessage({
reply: `Echo: ${event.data}`,
});
});
// main.js (페이지 스크립트)
navigator.serviceWorker.controller.postMessage("Hello SW!");
// Service Worker가 응답 보낸 걸 수신
navigator.serviceWorker.addEventListener("message", (event) => {
console.log("Reply from SW:", event.data.reply);
});
messageerror// serviceWorker.js
self.addEventListener("messageerror", (event) => {
console.error("Message failed:", event);
});
// main.js
try {
// 직렬화 불가능한 데이터(예: 함수)를 보내면 오류 발생
navigator.serviceWorker.controller.postMessage(() => {});
} catch (err) {
console.error("Message send failed:", err);
}
pushself.addEventListener("push", (event) => {
const data = event.data?.json() || { title: "Default title" };
event.waitUntil(
self.registration.showNotification(data.title, {
body: data.body || "Hello from Service Worker!",
icon: "/icon.png",
})
);
});
pushsubscriptionchangeself.addEventListener("pushsubscriptionchange", (event) => {
event.waitUntil(
self.registration.pushManager.subscribe({ userVisibleOnly: true })
.then((subscription) => {
// 새로운 구독 정보를 서버로 전송
return fetch("/update-subscription", {
method: "POST",
body: JSON.stringify(subscription),
});
})
);
});
notificationclickself.addEventListener("notificationclick", (event) => {
event.notification.close();
event.waitUntil(
clients.openWindow("/welcome") // 특정 경로 열기
);
});
notificationcloseself.addEventListener("notificationclose", (event) => {
console.log("Notification closed:", event.notification.tag);
});
sync// serviceWorker.js
self.addEventListener("sync", (event) => {
if (event.tag === "sendFormData") {
event.waitUntil(
fetch("/submit", {
method: "POST",
body: JSON.stringify({ message: "retry after offline" }),
headers: { "Content-Type": "application/json" },
})
);
}
});
// main.js (페이지)
navigator.serviceWorker.ready.then((registration) => {
return registration.sync.register("sendFormData");
});
이번 글에서는 Service Worker의 기본 개념부터 생명주기, 그리고 주요 이벤트까지 살펴보았습니다.
Service Worker는 PWA의 핵심 기술이지만, 개념만 보면 추상적으로 느껴지기 쉽습니다.
중요한 것은 언제 어떤 이벤트가 발생하고, 그 이벤트 안에서 무엇을 할 수 있는지를 이해하는 것입니다.
localhost 예외), 이는 보안상 필수 제약이다.install → waiting → activate → activated → redundant)를 이해하자.
좋은데요?