본문 바로가기
Frontend2024년 4월 5일9분 읽기

PWA 완벽 가이드 — 오프라인 지원과 푸시 알림 구현

YS
김영삼
조회 296

PWA(Progressive Web App)란?

PWA는 웹 기술로 네이티브 앱 수준의 사용자 경험을 제공하는 웹 애플리케이션입니다. 오프라인 지원, 푸시 알림, 홈 화면 설치 등 모바일 앱의 핵심 기능을 웹에서 구현할 수 있습니다. Google, Twitter, Starbucks 등 대형 서비스에서 이미 PWA를 도입하여 전환율을 크게 향상시켰습니다.

Service Worker 등록과 라이프사이클

Service Worker는 브라우저와 네트워크 사이에 위치하는 프록시 역할을 하며, 오프라인 캐싱과 푸시 알림의 핵심입니다. 등록 → 설치 → 활성화의 라이프사이클을 거칩니다.

// sw.js - Service Worker 파일
const CACHE_NAME = 'app-cache-v1';
const urlsToCache = [
  '/',
  '/styles/main.css',
  '/scripts/app.js',
  '/offline.html'
];

// Install 이벤트: 정적 자원 캐싱
self.addEventListener('install', (event) => {
  event.waitUntil(
    caches.open(CACHE_NAME)
      .then((cache) => {
        console.log('캐시 열림');
        return cache.addAll(urlsToCache);
      })
  );
  // 대기 중인 SW를 즉시 활성화
  self.skipWaiting();
});

// Activate 이벤트: 이전 캐시 정리
self.addEventListener('activate', (event) => {
  event.waitUntil(
    caches.keys().then((cacheNames) => {
      return Promise.all(
        cacheNames
          .filter((name) => name !== CACHE_NAME)
          .map((name) => caches.delete(name))
      );
    })
  );
  self.clients.claim();
});

// Fetch 이벤트: 캐시 우선, 네트워크 폴백
self.addEventListener('fetch', (event) => {
  event.respondWith(
    caches.match(event.request)
      .then((response) => {
        if (response) return response;
        return fetch(event.request).then((networkResponse) => {
          if (networkResponse.ok) {
            const cloned = networkResponse.clone();
            caches.open(CACHE_NAME)
              .then((cache) => cache.put(event.request, cloned));
          }
          return networkResponse;
        });
      })
      .catch(() => caches.match('/offline.html'))
  );
});

앱에서 Service Worker 등록하기

// main.js
if ('serviceWorker' in navigator) {
  window.addEventListener('load', async () => {
    try {
      const reg = await navigator.serviceWorker.register('/sw.js');
      console.log('SW 등록 성공:', reg.scope);
    } catch (err) {
      console.error('SW 등록 실패:', err);
    }
  });
}

캐싱 전략 비교

Service Worker에서 사용할 수 있는 주요 캐싱 전략은 다음과 같습니다. 각 전략은 콘텐츠 특성에 따라 선택해야 합니다.

전략설명적합한 경우
Cache First캐시 우선, 없으면 네트워크정적 자원 (CSS, JS, 이미지)
Network First네트워크 우선, 실패 시 캐시API 응답, 동적 콘텐츠
Stale While Revalidate캐시 반환 후 백그라운드 갱신자주 바뀌지만 최신 아니어도 되는 콘텐츠
Cache Only캐시에서만 응답오프라인 전용 페이지
Network Only항상 네트워크실시간 데이터 (결제 등)

Web Push 알림 구현

푸시 알림은 VAPID(Voluntary Application Server Identification) 키 쌍을 사용하여 서버와 브라우저 간 인증을 처리합니다.

VAPID 키 생성

npx web-push generate-vapid-keys

클라이언트 측 구독

async function subscribePush() {
  const reg = await navigator.serviceWorker.ready;
  const subscription = await reg.pushManager.subscribe({
    userVisibleOnly: true,
    applicationServerKey: urlBase64ToUint8Array(VAPID_PUBLIC_KEY)
  });
  // 서버에 구독 정보 전송
  await fetch('/api/push/subscribe', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(subscription)
  });
}

Service Worker에서 푸시 수신 처리

self.addEventListener('push', (event) => {
  const data = event.data.json();
  event.waitUntil(
    self.registration.showNotification(data.title, {
      body: data.body,
      icon: '/icons/icon-192.png',
      badge: '/icons/badge-72.png',
      data: { url: data.url }
    })
  );
});

self.addEventListener('notificationclick', (event) => {
  event.notification.close();
  event.waitUntil(
    clients.openWindow(event.notification.data.url)
  );
});

manifest.json 설정

PWA의 메타 정보를 정의하는 Web App Manifest 파일은 홈 화면 설치 시 앱 이름, 아이콘, 테마 색상 등을 지정합니다.

{
  "name": "My PWA App",
  "short_name": "MyApp",
  "start_url": "/",
  "display": "standalone",
  "background_color": "#ffffff",
  "theme_color": "#2196F3",
  "icons": [
    { "src": "/icons/icon-192.png", "sizes": "192x192", "type": "image/png" },
    { "src": "/icons/icon-512.png", "sizes": "512x512", "type": "image/png" }
  ]
}

실전 팁

  • Workbox 라이브러리를 사용하면 캐싱 전략을 선언적으로 관리할 수 있어 생산성이 크게 향상됩니다.
  • Lighthouse의 PWA 점수를 활용하여 PWA 호환성을 체크하세요.
  • iOS Safari는 Web Push를 iOS 16.4부터 지원하므로, 이전 버전 사용자에 대한 폴백 전략이 필요합니다.
  • 캐시 용량은 브라우저마다 다르므로, 캐시 용량 초과 시 LRU 정책으로 오래된 항목을 정리하세요.

댓글 0

아직 댓글이 없습니다.
Ctrl+Enter로 등록