
프론트엔드
Progressive Web App AdvancedPWA 심화
PWA(Progressive Web App) 심화는 Service Worker를 활용한 오프라인 지원, 앱 설치, 백그라운드 동기화, 푸시 알림 등 네이티브 앱 수준의 기능을 웹에서 구현하는 것을 다룬다.
Service Worker 캐싱 전략
javascript
// service-worker.js
const CACHE_NAME = 'app-v2';
const STATIC_ASSETS = ['/index.html', '/manifest.json', '/offline.html'];
// 설치: 정적 자산 사전 캐시
self.addEventListener('install', (event) => {
event.waitUntil(
caches.open(CACHE_NAME)
.then(cache => cache.addAll(STATIC_ASSETS))
.then(() => self.skipWaiting())
);
});
// 활성화: 이전 캐시 정리
self.addEventListener('activate', (event) => {
event.waitUntil(
caches.keys()
.then(keys => Promise.all(
keys.filter(k => k !== CACHE_NAME).map(k => caches.delete(k))
))
.then(() => self.clients.claim())
);
});
// 요청 가로채기
self.addEventListener('fetch', (event) => {
const url = new URL(event.request.url);
// API: Network First (최신 데이터 우선)
if (url.pathname.startsWith('/api/')) {
event.respondWith(
fetch(event.request)
.then(res => {
const clone = res.clone();
caches.open(CACHE_NAME).then(c => c.put(event.request, clone));
return res;
})
.catch(() => caches.match(event.request)) // 오프라인 폴백
);
return;
}
// 정적 자산: Cache First
event.respondWith(
caches.match(event.request)
.then(cached => cached || fetch(event.request))
.catch(() => caches.match('/offline.html'))
);
});앱 설치 (Install Prompt)
javascript
// manifest.json
{
"name": "My PWA",
"short_name": "PWA",
"start_url": "/",
"display": "standalone",
"theme_color": "#0070f3",
"background_color": "#ffffff",
"icons": [
{ "src": "/icon-192.png", "sizes": "192x192", "type": "image/png" },
{ "src": "/icon-512.png", "sizes": "512x512", "type": "image/png", "purpose": "maskable" }
],
"screenshots": [
{ "src": "/screenshot-wide.png", "sizes": "1280x720", "form_factor": "wide" }
]
}
// 설치 프롬프트 제어
let deferredPrompt;
window.addEventListener('beforeinstallprompt', (e) => {
e.preventDefault();
deferredPrompt = e;
showInstallButton();
});
installButton.addEventListener('click', async () => {
deferredPrompt.prompt();
const { outcome } = await deferredPrompt.userChoice;
console.log(outcome === 'accepted' ? '설치됨' : '거절됨');
deferredPrompt = null;
});푸시 알림
javascript
// 알림 구독
const registration = await navigator.serviceWorker.ready;
const subscription = await registration.pushManager.subscribe({
userVisibleOnly: true,
applicationServerKey: urlBase64ToUint8Array(PUBLIC_VAPID_KEY)
});
// 구독 정보를 서버에 전송
await fetch('/api/push-subscription', {
method: 'POST',
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: '/icon-192.png',
data: { url: data.url }
})
);
});
self.addEventListener('notificationclick', (event) => {
event.notification.close();
event.waitUntil(clients.openWindow(event.notification.data.url));
});관련 문서
- •[[web-components-advanced|웹 컴포넌트 심화]]
- •[[broadcast-channel|Broadcast Channel API]]
- •[[performance-budget|성능 버짓]]