jgjgill

서비스 워커(Service Worker)란?

No Filled

웹 페이지와 별도로 브라우저 백그라운드에서 실행되는 JavaScript 스크립트로, 네트워크 요청을 가로채어 오프라인 경험과 백그라운드 동기화를 가능하게 하는 기술

기본 개념

  • 웹 애플리케이션, 브라우저, 네트워크 사이에 위치한 프록시 서버 역할을 하는 스크립트
  • DOM에 직접 접근할 수 없으며, 독립적인 워커 컨텍스트에서 실행

등장 배경

  • 문제점: 기존 웹은 네트워크 연결이 끊기면 사용 불가능
  • AppCache의 한계: 이전 오프라인 솔루션인 AppCache는 유연성 부족, 예측 불가능한 동작
  • 네이티브 앱과의 격차: 푸시 알림, 백그라운드 동기화 등 네이티브 앱 기능 부재
  • 해결: 개발자가 네트워크 요청을 완전히 제어할 수 있는 프로그래밍 가능한 프록시 제공

동작 원리

1단계: 등록

  • 메인 스크립트에서 navigator.serviceWorker.register() 호출
  • 브라우저가 서비스 워커 파일 다운로드 시작

2단계: 설치

  • 처음 등록되거나 새 버전이 발견될 때 발생
  • install 이벤트에서 정적 자산 캐싱

3단계: 활성화

  • 이전 버전 정리 작업 수행
  • 오래된 캐시 삭제

4단계: 제어

  • 페이지의 네트워크 요청 가로채기
  • 캐시 우선, 네트워크 우선 등 전략 구현

관련 개념과 비교

Web Worker

  • Web Worker: 복잡한 연산 처리용, 페이지와 생명주기 동일
  • Service Worker: 네트워크 프록시, 페이지 종료 후에도 생존 가능

AppCache

  • AppCache: 선언적, 제한적, deprecated
  • Service Worker: 프로그래밍 방식, 완전한 제어 가능

코드 예시

서비스 워커 등록

// HTTPS 환경에서만 동작 (localhost 제외)
if ('serviceWorker' in navigator) {
  window.addEventListener('load', () => {
    navigator.serviceWorker
      .register('/service-worker.js')
      .then((registration) => {
        console.log('SW 등록 성공:', registration.scope)
      })
      .catch((error) => {
        console.log('SW 등록 실패:', error)
      })
  })
}

서비스 워커 파일

const CACHE_NAME = 'my-app-v1'
const urlsToCache = ['/', '/styles/main.css', '/scripts/app.js', '/images/logo.png']

// 설치 단계: 정적 자산 캐싱
self.addEventListener('install', (event) => {
  event.waitUntil(
    caches.open(CACHE_NAME).then((cache) => {
      console.log('캐시 열림')
      return cache.addAll(urlsToCache)
    }),
  )
  // 즉시 활성화 (개발 시)
  self.skipWaiting()
})

// 활성화 단계: 오래된 캐시 삭제
self.addEventListener('activate', (event) => {
  event.waitUntil(
    caches.keys().then((cacheNames) => {
      return Promise.all(
        cacheNames.map((cacheName) => {
          if (cacheName !== CACHE_NAME) {
            console.log('오래된 캐시 삭제:', cacheName)
            return caches.delete(cacheName)
          }
        }),
      )
    }),
  )
  // 모든 클라이언트 즉시 제어
  self.clients.claim()
})

// Fetch 단계: Cache First 전략
self.addEventListener('fetch', (event) => {
  event.respondWith(
    caches.match(event.request).then((response) => {
      // 캐시에 있으면 반환, 없으면 네트워크 요청
      return response || fetch(event.request)
    }),
  )
})

Network First 전략

// API 요청에 적합한 Network First 전략
self.addEventListener('fetch', (event) => {
  if (event.request.url.includes('/api/')) {
    event.respondWith(
      fetch(event.request)
        .then((response) => {
          // 성공 시 캐시에 저장
          const responseClone = response.clone()
          caches.open(CACHE_NAME).then((cache) => {
            cache.put(event.request, responseClone)
          })
          return response
        })
        .catch(() => {
          // 네트워크 실패 시 캐시 반환 (오프라인 대비)
          return caches.match(event.request)
        }),
    )
  }
})

헷갈리기 쉬운 부분

Service Worker ≠ Web Worker

  • Service Worker: 네트워크 프록시, 페이지와 독립적 생명주기
  • Web Worker: 계산 작업용, 페이지 종료 시 함께 종료

개념 정리

서비스 워커는 웹 애플리케이션과 네트워크 사이에서 프록시 역할을 하는 백그라운드 스크립트입니다.

주요 특징으로 페이지와 별도로 백그라운드에서 실행되며 DOM에 직접 접근할 수 없습니다. 그리고 네트워크 요청을 가로채서 캐싱 전략을 구현할 수 있어 오프라인 경험을 제공할 수 있습니다.

생명주기는 등록, 설치, 활성화, 제어의 4단계로 진행됩니다. 등록 시 브라우저가 서비스 워커 파일을 다운로드하고, 설치 단계에서 정적 자산을 캐싱합니다. 활성화 단계에서는 이전 버전의 캐시를 정리하고 이후 fetch 이벤트를 통해 네트워크 요청을 제어합니다.

PWA를 구현할 때 핵심 기술로 사용됩니다. 특히 Cache First 전략으로 정적 자산을 빠르게 제공하고 Network First 전략으로 API 응답의 최신성을 보장하면서도 오프라인 대비책을 마련할 수 있습니다.

보안상 HTTPS 환경에서만 동작하며 개발 시에는 localhost가 예외로 허용됩니다.

핵심 문장

  • 서비스 워커는 웹과 네트워크 사이의 프록시로 동작하는 백그라운드 스크립트
  • 네트워크 요청을 가로채서 캐싱을 제어 가능
  • 오프라인 경험과 빠른 로딩 속도를 제공
  • PWA의 핵심 기술
  • 등록-설치-활성화-제어의 생명주기
  • HTTPS에서만 동작
@2023 powered by jgjgill