웹 워커와 서비스 워커가 무엇인지, 어떻게 다른지, 언제 각각을 사용해야 하는지 알아보고 페이지 속도 개선, 백그라운드 작업, 캐싱 및 오프라인 지원을 구현하는 법을 배웁니다.

브라우저는 대부분의 자바스크립트를 메인 스레드에서 실행합니다—사용자 입력, 애니메이션, 페이지 페인팅도 같은 곳에서 처리됩니다. 여기서 무거운 작업이 발생하면(큰 데이터 파싱, 이미지 처리, 복잡한 계산) UI가 끊기거나 "정지"할 수 있습니다. 워커는 특정 작업을 메인 스레드 밖으로 옮기거나 페이지의 직접 통제에서 분리해 앱이 반응성을 유지하도록 돕습니다.
페이지가 200ms 계산을 수행하면 브라우저는 부드럽게 스크롤하거나 클릭에 반응하거나 60fps 애니메이션을 유지할 수 없습니다. 워커는 백그라운드에서 작업을 처리하는 동안 메인 스레드가 인터페이스에 집중하게 해 줍니다.
Web Worker는 페이지에서 생성하는 백그라운드 자바스크립트 스레드입니다. UI를 차단하는 CPU 집약적 작업에 적합합니다.
Service Worker는 웹 앱과 네트워크 사이에 위치하는 특별한 워커입니다. 요청을 가로채고 응답을 캐시하며, 오프라인 지원과 푸시 알림 같은 기능을 가능하게 합니다.
Web Worker는 다른 방에서 계산을 하는 도우미라고 생각하세요. 메시지를 보내면 작업을 처리하고 다시 메시지를 보냅니다.
Service Worker는 현관에 있는 게이트키퍼 같습니다. 페이지, 스크립트, API 호출에 대한 요청이 통과하고, 네트워크에서 가져올지, 캐시에서 제공할지, 혹은 맞춤 응답을 줄지 결정합니다.
이 글을 통해 다음을 알게 됩니다:
postMessage)이 워커 모델에서 어떻게 들어맞는지, 오프라인을 위해 Cache Storage API가 왜 중요한지이 개요는 "왜"와 사고 모델을 설정합니다—다음으로 각 워커 유형이 어떻게 동작하고 실제 프로젝트에서 어디에 들어맞는지 살펴보겠습니다.
페이지를 열면 대부분의 "느낌"은 메인 스레드에서 일어납니다. 픽셀을 그리기(렌더링), 탭/클릭에 반응하기(입력), 많은 자바스크립트 실행이 모두 여기서 처리됩니다.
렌더링, 입력 처리, 자바스크립트가 같은 스레드에서 번갈아 일어나기 때문에 한 번의 느린 작업이 모든 것을 기다리게 만듭니다. 그래서 성능 문제는 단순히 "코드가 느리다"가 아니라 반응성 문제로 나타납니다.
사용자가 체감하는 "블로킹":
JavaScript에는 fetch(), 타이머, 이벤트 같은 많은 비동기 API가 있어 대기 시간을 피하도록 돕습니다. 하지만 비동기가 무거운 작업을 렌더링과 동시에 자동으로 실행되게 하지는 않습니다.
이미지 처리, 큰 JSON 처리, 암호화, 복잡한 필터링같이 비싼 계산을 메인 스레드에서 하면 여전히 UI 업데이트와 경쟁합니다. "비동기"는 언제 실행될지를 미루어 줄 수 있지만, 여전히 같은 메인 스레드에서 실행되어 실행 시점에 잔상을 남길 수 있습니다.
워커는 브라우저가 페이지의 반응성을 유지하면서도 의미 있는 작업을 수행할 수 있게 해 줍니다.
요약하면: 워커는 메인 스레드를 보호해 앱이 백그라운드에서 실제 일을 하는 동안에도 인터랙티브하게 유지하게 해 줍니다.
Web Worker는 자바스크립트를 메인 스레드 밖에서 실행하는 방법입니다. UI 작업(렌더링, 스크롤, 클릭 반응)과 경쟁하지 않고 별도의 백그라운드 스레드에서 실행되어 무거운 작업이 페이지를 "멈추게" 하지 않습니다.
페이지는 사용자 상호작용에 집중하고, 워커는 큰 파일 파싱, 숫자 연산, 차트용 데이터 준비 같은 CPU 집약적 작업을 처리합니다.
Web Worker는 자체 전역 스코프를 가진 별도의 스레드에서 실행됩니다. 타이머, fetch(많은 브라우저에서), crypto 등 여러 웹 API에 접근할 수 있지만 페이지와는 의도적으로 분리되어 있습니다.
일반적으로 두 가지 형태가 있습니다:
처음 워커를 사용한다면 대부분의 예제가 Dedicated Worker를 보여줍니다.
워커는 페이지의 함수를 직접 호출하지 않습니다. 대신 메시지 전송으로 통신합니다:
postMessage()**로 워커에 데이터를 보냅니다.\n- 워커도 **postMessage()**로 다시 응답합니다.\n- 데이터는 structure clone 알고리즘으로 복제되며, 객체, 배열, 문자열, 숫자, Map/Set, ArrayBuffer 등 여러 타입을 지원합니다.큰 바이너리 데이터의 경우 ArrayBuffer 소유권을 이전(transfer)하면 복사가 아닌 전달로 성능을 개선할 수 있어 메시지 전달이 빠릅니다.
워커는 격리되어 있기 때문에 몇 가지 제약이 있습니다:
window나 document가 없고 self 전역이 있습니다. 사용 가능한 API가 페이지와 다를 수 있습니다.\n- 비동기적 사고방식: 모든 것이 메시지 기반이므로, 작업을 보내고 결과를 받는 식으로 코드를 구조화해야 합니다.잘 사용하면 Web Worker는 앱의 동작을 바꾸지 않고(단지 비싼 작업의 위치만 바꿔) 메인 스레드 성능을 향상시키는 가장 간단한 방법 중 하나입니다.
Web Worker는 페이지가 자바스크립트 때문에 "멈췄다"고 느껴질 때 매우 유용합니다. 메인 스레드는 사용자 상호작용과 렌더링을 책임지므로 그곳에서 무거운 작업을 하면 잔상, 클릭 지연, 스크롤 정지 등이 발생합니다.
직접 DOM 접근이 필요 없는 CPU 집약적 작업에 Web Worker를 사용하세요:\n
실용적 예: 큰 JSON 페이로드를 받아 파싱할 때 UI가 끊긴다면 파싱을 워커로 옮기고 결과만 전송하세요.
워커와의 통신은 postMessage로 이뤄집니다. 큰 바이너리 데이터는 복사를 피하기 위해 전달 가능한 객체(transferable)(예: ArrayBuffer)를 사용하세요.\n
// main thread
worker.postMessage(buffer, [buffer]); // ArrayBuffer를 전달
오디오 버퍼, 이미지 바이트 등 큰 데이터에 특히 유용합니다.
워커는 오버헤드가 있습니다: 추가 파일, 메시지 전달, 다른 디버깅 흐름 등. 다음 경우에는 생략하세요:\n
postMessage 왕복은 이점을 상쇄할 수 있습니다)작업이 눈에 띄는 정지(대략 50ms 이상)를 유발하고 "입력 → 계산 → 출력"으로 표현 가능하며 DOM 접근이 필요 없다면 Web Worker를 고려하세요. 작업이 주로 UI라면 메인 스레드에서 최적화하세요.
Service Worker는 브라우저의 백그라운드에서 실행되는 특별한 자바스크립트 파일로, 사이트의 프로그래머블 네트워크 계층처럼 동작합니다. 페이지 자체에서 실행되는 대신, 웹 앱과 네트워크 사이에 자리하여 리소스(HTML, CSS, API 호출, 이미지)에 대한 요청이 있을 때 어떤 동작을 할지 결정할 수 있습니다.
Service Worker는 어떤 한 탭과 분리된 수명을 가집니다:\n
브라우저가 언제든지 워커를 멈추고 다시 시작할 수 있으므로 이벤트 중심 스크립트로 다루세요: 작업은 빠르게 수행하고 상태는 영속 저장소에 보관하며 항상 항상 실행 중이라고 가정하지 마세요.
Service Worker는 **동일 출처(origin)**로 제한되며 워커 파일이 제공되는 폴더(및 그 하위)에서 보통 스코프를 가집니다. 또한 네트워크 요청을 조작할 수 있기 때문에 HTTPS가 필요합니다(로컬 개발에서는 localhost 예외).
Service Worker는 주로 웹 앱과 네트워크 사이에 위치해 리소스 요청에 대해 결정을 내립니다. 네트워크를 사용할지, 캐시된 데이터를 쓸지, 백그라운드에서 약간의 작업을 수행할지 등을 판별할 수 있습니다.
가장 흔한 역할은 자산과 응답을 캐시해 오프라인 또는 불안정한 연결 환경에서 경험을 개선하는 것입니다.
몇 가지 실용적 캐싱 전략:\n
이는 보통 Cache Storage API와 fetch 이벤트 핸들링으로 구현됩니다.
Service Worker는 재방문 시 체감 속도를 개선할 수 있습니다:\n
결과적으로 네트워크 요청이 줄고 시작 속도가 빨라지며 불안정한 연결에서도 더 일관된 성능을 제공할 수 있습니다.
Service Worker는 푸시 알림이나 백그라운드 동기화 같은 백그라운드 기능을 제공할 수 있습니다(브라우저/플랫폼별 지원 차이 있음). 즉, 페이지가 열려 있지 않아도 사용자에게 알림을 보내거나 실패한 요청을 나중에 재시도할 수 있습니다.
프로그레시브 웹 앱을 만드는 데 있어서 Service Worker는 핵심 요소입니다:\n
한 가지만 기억하세요: Web Worker는 UI를 멈추지 않고 무거운 작업을 수행하게 도와주고, Service Worker는 네트워크 요청을 제어하고 설치 가능한 앱(PWA)처럼 동작하게 합니다.
fetch()를 호출할 수는 있지만 다른 부분의 요청을 재작성하거나 서빙할 수는 없습니다.\n- Service Worker는 fetch 이벤트로 네트워크 요청을 가로채 네트워크 사용/캐시 제공/폴백 응답 등을 결정할 수 있습니다.워커를 실행하려면 주로 어디서 실행되는지와 어떻게 로드되는지를 신경 써야 합니다. Web Worker는 페이지 스크립트에서 직접 생성합니다. Service Worker는 페이지에서 등록되어 브라우저가 설치하도록 합니다.
Web Worker는 페이지가 생성할 때 시작됩니다. 별도의 자바스크립트 파일을 가리키고 postMessage로 통신합니다.
// main.js (페이지에서 실행)
const worker = new Worker('/workers/resize-worker.js', { type: 'module' });
worker.postMessage({ action: 'start', payload: { /* ... */ } });
worker.onmessage = (event) => {
console.log('From worker:', event.data);
};
좋은 사고 모델: 워커 파일은 페이지가 가져올 수 있는 또 다른 스크립트 URL이지만, 메인 스레드 밖에서 실행됩니다.
Service Worker는 사용자가 방문하는 페이지에서 등록해야 합니다:
// main.js
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('/sw.js');
}
등록 이후 브라우저가 설치/활성화 라이프사이클을 처리합니다. sw.js는 install, activate, fetch 같은 이벤트를 수신할 수 있습니다.
Service Worker는 네트워크 요청을 가로채고 응답을 캐시할 수 있습니다. HTTP에서 등록이 허용되면 네트워크 공격자가 악의적인 sw.js를 주입해 이후 방문을 제어할 수 있습니다. HTTPS(개발 시 http://localhost 예외)는 스크립트와 영향을 받는 트래픽을 보호합니다.
브라우저는 워커를 일반 스크립트와 다르게 캐시하고 업데이트합니다. 업데이트 계획을 세우세요:\n
sw.js/워커 번들 배포).\n- Service Worker는 업데이트 플로우가 있습니다: 새 워커가 설치되고 안전할 때 활성화됩니다.\n- 캐싱 규칙을 변경할 때 활성화 단계에서 이전 캐시를 정리하는 로직을 포함해 오래된 캐시가 남지 않도록 하세요.나중에 더 부드러운 롤아웃 전략을 원하면, 테스트 습관과 업데이트 엣지 케이스를 잡는 방법은 /blog/debugging-workers를 참조하세요.
워커는 "일반" 페이지 자바스크립트와 다르게 실패합니다: 별도의 컨텍스트에서 실행되고 자체 콘솔을 가지며 브라우저가 재시작할 수 있습니다. 탄탄한 디버깅 루틴은 많은 시간을 절약합니다.
DevTools에서 워커 전용 타겟을 찾으세요. Chrome/Edge에서는 보통 Sources 아래나 콘솔 컨텍스트 선택기에서 워커를 볼 수 있습니다.
메인 스레드에서 쓰는 동일한 도구를 사용하세요:\n
onmessage 핸들러와 긴 함수들을 단계별로 진행하세요.\n- 퍼포먼스 프로파일링: Performance 트레이스를 기록해 메인 스레드가 워커 작업 동안 반응성을 유지하는지 확인하세요.메시지가 사라지는 것처럼 보이면 양쪽을 점검하세요: worker.postMessage(...)를 호출하는지, 워커에 self.onmessage = ...가 있는지, 메시지 형태가 맞는지 확인하세요.
Service Worker는 Application 패널에서 디버깅하는 것이 좋습니다:\n
설치/활성화/페치 오류는 콘솔에서 확인하면 캐싱이나 오프라인 동작 문제의 원인을 자주 설명합니다.
캐싱 문제는 가장 많은 시간을 잡아먹습니다: 잘못 캐시된 파일(또는 너무 과도한 캐시)은 오래된 HTML/JS를 유지할 수 있습니다. 테스트 중에는 하드 리로드를 시도해 무엇이 실제로 서빙되는지 확인하세요.
현실적인 테스트를 위해 DevTools로:\n
PWA를 빠르게 반복 개발할 때는 예측 가능한 Service Worker와 빌드 출력이 있는 기본 앱을 생성한 뒤 캐싱 전략을 점진적으로 다듬는 게 도움이 됩니다. Koder.ai 같은 플랫폼은 이런 실험에 유용할 수 있습니다: 채팅 프롬프트로 React 기반 웹 앱을 프로토타입하고 소스 코드를 내보낸 뒤 워커 설정과 캐싱 규칙을 빠르게 조정할 수 있습니다.
워커는 앱을 더 부드럽고 강력하게 만들 수 있지만 코드가 어디서 실행되는지와 접근 범위를 바꿉니다. 보안, 프라이버시, 성능을 빠르게 점검하면 예상치 못한 버그와 불만족스러운 사용자 경험을 피할 수 있습니다.
Web Worker와 Service Worker 모두 동일 출처 정책에 의해 제한됩니다: 기본적으로 같은 스킴/호스트/포트의 리소스만 직접 다룰 수 있습니다(서버가 CORS로 명시적으로 허용한 경우 예외). 이는 워커가 다른 사이트에서 조용히 데이터를 끌어와 앱에 섞는 것을 막습니다.
Service Worker는 추가적인 보호 장치가 있습니다: 보통 HTTPS를 요구(개발 시 localhost 제외)하며 네트워크 요청을 가로챌 수 있기 때문에 권한이 높은 코드로 취급하세요. 의존성을 최소화하고 동적 코드 로딩을 피하며 캐싱 로직을 신중히 버전 관리해 오래된 캐시가 계속 제공되지 않도록 하세요.
백그라운드 기능은 예측 가능해야 합니다. 푸시 알림은 강력하지만 권한 요청은 남용하기 쉽습니다.
권한은 명확한 이득이 있을 때만 요청하세요(예: 사용자가 설정에서 알림을 켠 이후). 백그라운드에서 데이터 동기화나 프리페치(prefetch)를 한다면 네트워크 활동이나 알림에 대해 사용자가 알아차릴 수 있으니 평이한 언어로 알리세요.
워커가 항상 "무료"는 아닙니다. 과도한 사용은 역효과를 낼 수 있습니다:\n
postMessage 호출(특히 큰 객체와 함께)은 병목이 될 수 있습니다. 배칭과 transferables 사용을 권장합니다.\n- 메모리 비용: 각 워커는 자체 메모리와 시작 오버헤드가 있어 워커가 너무 많으면 RAM 사용과 배터리 소모가 증가합니다.\n- 캐시 증가: 과도하게 캐시하면 저장 공간이 불어나므로 업데이트 시 캐시 제한과 정리 로직을 추가하세요.모든 브라우저가 모든 기능을 지원하는 건 아닙니다(또는 사용자가 권한을 차단할 수 있음). 기능 감지를 하고 우아하게 퇴화하세요:
if ('serviceWorker' in navigator) {
// service worker 등록
} else {
// 오프라인 기능 없이 계속 동작
}
목표는: 핵심 기능은 여전히 작동하게 두고, 오프라인/푸시/무거운 계산처럼 "있으면 좋은" 기능을 가능할 때 추가하는 것입니다.
Web Worker와 Service Worker는 문제를 다르게 해결하므로, 앱에 무거운 계산과 빠르고 신뢰할 수 있는 로딩이 모두 필요할 때 함께 쓰기 좋습니다. 좋은 사고 모델은: Web Worker = 계산, Service Worker = 네트워크 + 캐싱, 메인 스레드 = UI입니다.
사용자가 사진을 편집하고(리사이즈, 필터, 배경 제거) 오프라인 시에도 갤러리를 보기를 원한다고 가정합니다.
이 "계산 후 캐시" 접근법은 책임을 명확히 유지합니다: 워커는 출력을 만들고 서비스 워커는 어떻게 저장하고 제공할지 결정합니다.
피드, 폼, 현장 데이터가 있는 앱의 경우:\n
완전한 백그라운드 동기화가 없더라도 서비스 워커는 캐시된 응답을 제공해 사용자가 즉시 내용을 보게 하고, 앱은 배경에서 업데이트할 수 있습니다.
혼합을 피하세요:\n
postMessage를 통해).\n- Service Worker: 요청 라우팅, 캐싱 전략, 오프라인 폴백.아니요. Service Worker는 페이지 탭과 분리된 백그라운드에서 실행되며 페이지의 HTML 요소에 직접 접근할 수 없습니다.
이 분리는 의도적입니다: 예를 들어 푸시 이벤트에 응답하거나 캐시된 파일을 제공하기 위해 페이지가 열려 있지 않을 수 있기 때문에 브라우저는 워커를 격리합니다.
Service Worker가 사용자에게 보이는 것에 영향을 주어야 하면 postMessage 같은 메시징으로 페이지와 통신하게 하고, 페이지가 UI를 업데이트하게 하세요.
아니요. 둘은 독립적입니다.\n\n- Web Worker: UI 반응성을 유지하기 위해 무거운 자바스크립트 작업을 메인 스레드 밖으로 옮깁니다.\n- Service Worker: 네트워크 수준 기능(오프라인 캐싱, 요청 가로채기, 백그라운드 동기화, 푸시)을 제공합니다.
단독으로 하나만 사용하거나, 둘 다 필요한 경우 함께 사용할 수 있습니다.
대부분 최신 브라우저에서 Web Worker는 널리 지원됩니다. 기본적인 호환성은 높습니다.\n\nService Worker도 주요 브라우저 최신 버전에서 널리 지원되지만 몇 가지 요구사항과 엣지 케이스가 있습니다:\n
localhost 예외).\n- 푸시 알림 같은 일부 기능은 브라우저와 플랫폼에 따라 다르게 동작합니다.넓은 호환성이 중요하면 Service Worker 기능을 점진적 향상(progressive enhancement) 관점에서 추가하세요: 핵심 경험을 먼저 구축하고 오프라인/푸시는 가능한 곳에서 더하세요.
아니요. 자동으로 빨라지지는 않습니다.\n\n- Web Worker: 사이트가 메인 스레드에서 CPU 집약적 자바스크립트 때문에 느려지는 경우 도움이 됩니다. 그 작업을 옮기면 잔상이 줄고 반응성이 좋아지지만, 데이터 전송 비용 같은 오버헤드는 여전히 있습니다.\n- Service Worker: 네트워크 요청이 병목인 경우 도움이 됩니다. 캐싱과 스마트한 페치 처리는 재방문 시 즉각적인 체감 속도를 줄 수 있지만, 잘못된 캐싱은 오래된 콘텐츠를 제공할 수 있습니다.
진짜 이득은 병목에 맞는 올바른 워커를 골라 적용하고, 적용 전후로 측정할 때 나옵니다.
입력 → 계산 → 출력 형태로 표현할 수 있고 DOM 접근이 필요 없는, CPU 집약적 작업이 있을 때 Web Worker를 사용하세요.
적합한 예: 큰 페이로드 파싱/변환, 압축/압축 해제, 암호화 작업, 이미지/오디오 처리, 복잡한 필터링 등입니다. 작업이 주로 UI 업데이트나 잦은 DOM 읽기/쓰기라면 워커는 도움이 되지 않으며(워커는 DOM에 접근할 수 없습니다) 오히려 복잡도만 늘릴 수 있습니다.
오프라인 지원, 캐싱 전략, 반복 방문 시 빠른 로드, 요청 라우팅, 그리고(지원되는 경우) 푸시/백그라운드 동기화 같은 네트워크 제어가 필요하면 Service Worker를 사용하세요.
요약: UI가 계산 때문에 멈춘다면 Web Worker가 해결책이고, 로딩이 느리거나 오프라인 동작이 문제라면 Service Worker가 적합합니다.
아니요. Web Worker와 Service Worker는 독립적인 기능입니다.
둘 중 하나만 사용해도 되고, 계산과 네트워크 기능이 모두 필요하면 두 가지를 함께 사용할 수 있습니다.
가장 큰 차이는 범위와 수명입니다.
fetch 같은 이벤트가 발생하면 브라우저가 깨워 처리하고, 일이 끝나면 다시 정지합니다. 즉 페이지가 열려 있지 않아도 동작할 수 있습니다.아니요. Web Worker는 window/document에 접근할 수 없습니다.
UI에 영향을 주어야 한다면 결과를 메인 스레드로 postMessage()로 보내고 페이지 코드에서 DOM을 업데이트하세요. 워커는 순수 계산에 집중하도록 유지하세요.
아니요. Service Worker도 DOM에 접근할 수 없습니다.
사용자에게 보이는 내용을 바꾸려면 Clients API와 postMessage() 등을 통해 제어되는 페이지와 통신하고, 페이지가 UI를 업데이트하게 하세요.
양쪽 모두에서 postMessage()를 사용하세요.
worker.postMessage(data)self.postMessage(result)큰 바이너리 데이터를 보낼 때는 복사를 피하기 위해 transferable(예: ArrayBuffer)를 사용하세요:
Service Worker는 앱과 네트워크 사이에 위치해 요청을 가로채고 Cache Storage API로 응답을 저장/제공할 수 있습니다.
일반적인 전략:
리소스 타입(앱 셸 vs API 데이터)마다 전략을 선택하세요. 하나의 규칙으로 모두 처리하지 마세요.
네. 단, 책임을 분명히 하세요.
일반 패턴:
이렇게 하면 백그라운드와 네트워크 책임이 혼합되는 일을 피할 수 있습니다.
각각의 DevTools 영역을 사용하세요.
onmessage에 브레이크포인트를 설정해 메시지 흐름과 긴 작업을 점검하세요. 퍼포먼스 프로파일링으로 메인 스레드가 워커 작업 동안 응답성을 유지하는지 확인하세요.캐싱 버그를 디버깅할 때는 실제로 무엇이 서빙되는지(네트워크 vs 캐시)를 꼭 확인하고, 오프라인/스로틀링 테스트를 수행하세요.
worker.postMessage(buffer, [buffer]);