“브라우저를 닫았는데도 어떻게 알림이 오지?” PWA를 구현하다가 문뜩 궁금해졌습니다. 분명 페이지는 닫았는데, 알림이 옵니다. 이 글은 그 순간의 의문에서 시작되었습니다.
서비스 워커가 어떻게 백그라운드에서 동작하는지, 왜 새 코드가 바로 반영되지 않는지, 그리고 언제 기존 캐시와 새로운 캐시가 동기화되는지까지 단계별로 살펴보려 합니다.
서비스 워커는 브라우저 백그라운드에서 동작하는 자바스크립트 스크립트입니다.
웹 애플리케이션과 네트워크 사이에서 프록시 역할을 하며, 네트워크 요청을 가로채거나 캐시된 리소스로 응답할 수 있습니다.
가장 큰 특징은 서비스 워커가 메인 스레드와 완전히 분리된 백그라운드 스레드에서 실행된다는 점입니다. 즉, 사용자 인터페이스를 담당하는 메인 스레드의 렌더링 작업에 영향을 주지 않고 비동기적으로 동작합니다.
브라우저는 기본적으로 하나의 메인 스레드에서 대부분의 작업을 수행합니다. 이 스레드는 HTML 파싱, 스타일 계산, 레이아웃, 페인트, 자바스크립트 실행 등 렌더링과 로직을 모두 처리합니다.
문제는 네트워크가 느리거나 오프라인일 때 발생합니다. 이 경우, 웹은 빈 화면을 보여주거나 “인터넷 연결이 없다”라는 메시지를 띄울 수 밖에 없었습니다.
웹은 네트워크 의존적이며 일시적인 환경 변화에 취약한 구조입니다. 이런 특성 때문에 웹앱은 네이티브 앱에 비해 다음과 같은 한계를 가지고 있었습니다.
이런 한계를 극복하고, 웹에서도 네이티브 앱 수준의 사용자 경험을 제공하기 위해 등장한 기술이 바로 서비스 워커입니다.
서비스 워커의 주요 목표는 다음과 같습니다.
이 세가지 기능 덕분에 서비스 워커는 PWA의 핵심 구성 요소로 자리 잡게 되었습니다.
서비스 워커는 웹 애플리케이션과 네트워크 사이의 중간 계층(Proxy Layer) 으로 동작합니다.
즉, 브라우저가 네트워크로 요청을 보내기 전에 서비스 워커가 개입하여 요청을 가로채고, 필요에 따라 캐시된 리소스에서 응답하거나 새로운 네트워크 요청을 수행합니다. 이를 통해 오프라인 환경에서도 앱이 동작할 수 있고, 네트워크 지연 없이 빠른 로딩이 가능합니다.
그 핵심은 서비스 워커의 명확한 생명주기에 있는데요. 이에 대해 알아보도록 하겠습니다.
서비스 워커는 일반 스크립트와 달리, 명확한 생명주기 단계를 거쳐 등록/활성화/갱신됩니다. 각 단계는 다음과 같습니다.
| 단계 | 설명 |
|---|---|
| 1. 등록(Register) | navigator.serviceWorker.register('/sw.js')로 브라우저에 서비스 워커를 등록 |
| 2. 설치(Install) | 서비스 워커 파일 다운로드 및 설치, install 이벤트에서 필요한 정적 자산 캐싱 |
| 3. 대기(Waiting) | 새 서비스 워커가 설치 완료 후, 기존 워커가 여전히 활성 상태이면 대기 상태로 머뭄 |
| 4. 활성화(Activate) | 기존 워커가 해제되면 새로운 워커가 활성화되어 activate 이벤트 발생, 이전 버전 캐시를 정리 |
| 5. 동작(Working) | 네트워크 요청 가로채기, 메시지 처리 등 제어 |
즉, 새로운 서비스 워커는 바로 적용되지 않고 기존 워커가 제거된 뒤, 다음 새로고침 혹은 모든 탭이 닫힌 이후에 반영됩니다.
이런 구조 덕분에 브라우저는 안정성을 유지하면서도 점진적인 업데이트를 수행할 수 있습니다. 그렇다면 이런 서비스 워커는 어떤 특징을 가지고 있을까요?
서비스 워커는 보안상의 이유로 HTTPS 환경에서만 동작합니다.
이는 중간자 공격을 방지하기 위함이며, 예외적으로 개발 환경의 localhost에서만 예외적으로 허용됩니다.
서비스 워커는 상시 실행되는 스크립트가 아닙니다. 필요할 때만 브라우저가 워커를 로드하여 이벤트를 처리하고, 작업이 끝나면 자동으로 종료되어 메모리 사용량을 최소화합니다.
대표적인 이벤트는 다음과 같습니다.
| 이벤트 | 역할 |
|---|---|
| install | 초기 리소스 캐싱 |
| activate | 이전 캐시 정리, 새 버전 적용 |
| fetch | 네트워크 요청 가로채기 및 캐시 응답 |
| push | 푸시 알림 수신 |
| sync | 백그라운드 데이터 동기화 |
서비스 워커는 별도의 스레드에서 실행되기 때문에 DOM에 직접 접근할 수 없습니다. 페이지와 데이터를 주고 받기 위해서는 postMessage API를 사용하여 메시지 기반 통신을 수행해야 합니다.
서비스 워커가 실제로 푸시 알림을 수신하고 표시하는 과정은 다음과 같습니다.
navigator.serviceWorker.register()로 서비스 워커를 등록하고 Notification.requestPermission()으로 알림 권한을 요청합니다.PushManager.subscribe를 통해 사용자의 브라우저와 디바이스를 식별할 수 있는 푸시 구독 정보를 생성합니다. 이 구독 정보에는 브라우저의 푸시 서버(Google, Apple 등)의 엔드포인트가 포함됩니다.self.registration.showNotification()을 호출해 실제 알림을 표시합니다.이처럼 서비스 워커는 눈에 보이지 않지만, 브라우저와 서버 사이에서 조용히 일하면서 오프라인 경험과 푸시 기능을 가능하게 합니다.
(그림 추가 예정)
그렇다면 PWA를 배포한 뒤, 새 코드가 언제 적용되고, 왜 바로 반영되지 않는지 궁금했던 적이 있을 겁니다. 이 부분은 앞서 살펴본 서비스 워커의 생명주기와 밀접한 관련이 있습니다.
서비스 워커 업데이트 흐름은 대략 다음과 같습니다.
결국 “탭을 닫고 새로 열 때” 비로소 새 코드가 완전히 반영됩니다. 이는 캐시의 일관성을 보장하기 위한 설계이며, 필요할 경우 위 두 메서드로 강제로 갱신할 수도 있습니다.
마지막으로, 이 글의 출발점이었던 질문으로 돌아가 봅시다.
“브라우저를 닫았는데도 어떻게 알림이 오는 걸까?”
답을 바로 이야기 하자면, 브라우저의 탭이 닫혀도 OS 레벨의 푸시 서비스와 브라우저 백그라운드 프로세스는 여전히 살아 있기 때문입니다.
보통의 사용자가 브라우저를 닫았다 라고 느끼는 상황은 단순 탭이나 창을 닫는 행위입니다. 하지만 실제로는 닫히지 않고 있는데요. 다음과 같은 상황들이 존재합니다.
| 행동 | 실제 내부 상태 | 푸시 알림 가능 여부 |
|---|---|---|
| 탭 닫기 (x만 누름) | 해당 렌더러 프로세스만 종료, 브라우저 백그라운드는 계속 유지 | ✅ 가능 |
| 창 전체 닫기 (Command + W) | 렌더러 여러 개 종료, 브라우저 메인 프로세스는 여전히 백그라운드 실행 | ✅ 가능 |
| 완전 종료 (Command + Q / Ctrl + Q) | 브라우저 프로세스 자체가 종료됨 | ❌ 불가능 |
즉, 창을 닫는 것과 브라우저를 완전히 종료하는 것은 큰 차이가 있습니다. 탭은 사라졌더라도 백그라운드 프로세스와 OS의 푸시 데몬은 여전히 동작중입니다. 따라서, 이 프로세스가 푸시 서버와의 연결을 유지하고 있다가, 새 푸시 이벤트가 수신되면 서비스 워커를 깨워 실행합니다.
정리하자면 다음과 같습니다.
따라서 브라우저를 껐음에도 알림을 받을 수 있었던 것입니다.
알림이 오는 장면을 녹화해 보려 했지만, 이상하게 화면 녹화를 시작하면 알림이 표시되지 않았습니다. 이는 정상적인 현상으로, 화면 녹화 도구가 보안 및 개인정보 보호 정책상 ‘시스템 UI’를 녹화하지 않도록 설계되어 있기 때문이라고 합니다.
QuickTime, OBS, 기본 캡처 등 대부분의 녹화 프로그램은
화면 내 콘텐츠만 캡쳐하고, OS가 표시하는 알림·토스트·시스템 팝업은 제외합니다.
따라서 실제 녹화 영상에는 알림이 나타나지 않습니다.
서비스 워커를 공부하다 보니 학부 시절 배웠던 운영체제가 떠올랐습니다.
결국 어떤 기술이든 근본적인 지식 위에서 이해될 때 더 단단해진다는 걸 다시 느꼈던 것 같아요.
서비스 워커의 동작을 OS 관점에서 바라보니, 이전보다 훨씬 깊이 있는 질문들을 스스로 던질 수 있었고, 그 과정이 오랜만에 꽤 즐거웠습니다!