pwa

star 13

Progressive Web App 구현 가이드를 실행합니다.

excatt By excatt schedule Updated 4/8/2026

name: "pwa" description: "Progressive Web App 구현 가이드를 실행합니다." user-invocable: true

PWA Skill

Progressive Web App 구현 가이드를 실행합니다.

PWA 핵심 요소

1. Web App Manifest - 앱 정보
2. Service Worker - 오프라인, 캐싱
3. HTTPS - 보안
4. Responsive - 반응형 디자인

Web App Manifest

manifest.json

{
  "name": "My PWA App",
  "short_name": "MyApp",
  "description": "앱 설명",
  "start_url": "/",
  "display": "standalone",
  "background_color": "#ffffff",
  "theme_color": "#000000",
  "orientation": "portrait-primary",
  "icons": [
    {
      "src": "/icons/icon-72x72.png",
      "sizes": "72x72",
      "type": "image/png"
    },
    {
      "src": "/icons/icon-192x192.png",
      "sizes": "192x192",
      "type": "image/png",
      "purpose": "any maskable"
    },
    {
      "src": "/icons/icon-512x512.png",
      "sizes": "512x512",
      "type": "image/png"
    }
  ],
  "screenshots": [
    {
      "src": "/screenshots/desktop.png",
      "sizes": "1280x720",
      "type": "image/png",
      "form_factor": "wide"
    },
    {
      "src": "/screenshots/mobile.png",
      "sizes": "640x1136",
      "type": "image/png",
      "form_factor": "narrow"
    }
  ],
  "shortcuts": [
    {
      "name": "새 항목",
      "url": "/new",
      "icons": [{ "src": "/icons/new.png", "sizes": "192x192" }]
    }
  ]
}

HTML 연결

<head>
  <link rel="manifest" href="/manifest.json">
  <meta name="theme-color" content="#000000">
  <meta name="apple-mobile-web-app-capable" content="yes">
  <meta name="apple-mobile-web-app-status-bar-style" content="black">
  <link rel="apple-touch-icon" href="/icons/icon-192x192.png">
</head>

Service Worker

등록

// app.js
if ('serviceWorker' in navigator) {
  window.addEventListener('load', async () => {
    try {
      const registration = await navigator.serviceWorker.register('/sw.js');
      console.log('SW registered:', registration.scope);
    } catch (error) {
      console.error('SW registration failed:', error);
    }
  });
}

Service Worker 구현

// sw.js
const CACHE_NAME = 'my-app-v1';
const STATIC_ASSETS = [
  '/',
  '/index.html',
  '/styles.css',
  '/app.js',
  '/icons/icon-192x192.png',
];

// 설치 - 정적 자원 캐시
self.addEventListener('install', (event) => {
  event.waitUntil(
    caches.open(CACHE_NAME).then((cache) => {
      return cache.addAll(STATIC_ASSETS);
    })
  );
  self.skipWaiting();
});

// 활성화 - 이전 캐시 삭제
self.addEventListener('activate', (event) => {
  event.waitUntil(
    caches.keys().then((keys) => {
      return Promise.all(
        keys
          .filter((key) => key !== CACHE_NAME)
          .map((key) => caches.delete(key))
      );
    })
  );
  self.clients.claim();
});

// Fetch - 캐시 전략
self.addEventListener('fetch', (event) => {
  event.respondWith(
    caches.match(event.request).then((cached) => {
      // Cache First
      if (cached) return cached;

      return fetch(event.request).then((response) => {
        // 성공적인 응답만 캐시
        if (!response || response.status !== 200) {
          return response;
        }

        const responseClone = response.clone();
        caches.open(CACHE_NAME).then((cache) => {
          cache.put(event.request, responseClone);
        });

        return response;
      });
    })
  );
});

캐싱 전략

Cache First (오프라인 우선)

// 정적 자원에 적합
event.respondWith(
  caches.match(event.request).then((cached) => {
    return cached || fetch(event.request);
  })
);

Network First (최신 데이터 우선)

// API 응답에 적합
event.respondWith(
  fetch(event.request)
    .then((response) => {
      const clone = response.clone();
      caches.open(CACHE_NAME).then((cache) => {
        cache.put(event.request, clone);
      });
      return response;
    })
    .catch(() => caches.match(event.request))
);

Stale While Revalidate

// 빠른 응답 + 백그라운드 업데이트
event.respondWith(
  caches.open(CACHE_NAME).then((cache) => {
    return cache.match(event.request).then((cached) => {
      const fetchPromise = fetch(event.request).then((response) => {
        cache.put(event.request, response.clone());
        return response;
      });
      return cached || fetchPromise;
    });
  })
);

Workbox (권장)

설정

// sw.js with Workbox
importScripts('https://storage.googleapis.com/workbox-cdn/releases/6.5.4/workbox-sw.js');

const { precacheAndRoute } = workbox.precaching;
const { registerRoute } = workbox.routing;
const { CacheFirst, NetworkFirst, StaleWhileRevalidate } = workbox.strategies;
const { ExpirationPlugin } = workbox.expiration;

// Precache
precacheAndRoute(self.__WB_MANIFEST);

// 이미지 - Cache First
registerRoute(
  ({ request }) => request.destination === 'image',
  new CacheFirst({
    cacheName: 'images',
    plugins: [
      new ExpirationPlugin({ maxEntries: 50, maxAgeSeconds: 30 * 24 * 60 * 60 }),
    ],
  })
);

// API - Network First
registerRoute(
  ({ url }) => url.pathname.startsWith('/api/'),
  new NetworkFirst({
    cacheName: 'api',
    plugins: [
      new ExpirationPlugin({ maxEntries: 100, maxAgeSeconds: 24 * 60 * 60 }),
    ],
  })
);

Push Notifications

구독

async function subscribePush() {
  const registration = await navigator.serviceWorker.ready;

  const subscription = await registration.pushManager.subscribe({
    userVisibleOnly: true,
    applicationServerKey: urlBase64ToUint8Array(VAPID_PUBLIC_KEY),
  });

  // 서버에 구독 정보 전송
  await fetch('/api/push/subscribe', {
    method: 'POST',
    body: JSON.stringify(subscription),
    headers: { 'Content-Type': 'application/json' },
  });
}

수신 (Service Worker)

self.addEventListener('push', (event) => {
  const data = event.data.json();

  event.waitUntil(
    self.registration.showNotification(data.title, {
      body: data.body,
      icon: '/icons/icon-192x192.png',
      badge: '/icons/badge.png',
      data: { url: data.url },
    })
  );
});

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

Next.js PWA

next-pwa

// next.config.js
const withPWA = require('next-pwa')({
  dest: 'public',
  disable: process.env.NODE_ENV === 'development',
  register: true,
  skipWaiting: true,
});

module.exports = withPWA({
  // Next.js config
});

체크리스트

필수

  • HTTPS
  • Web App Manifest
  • Service Worker
  • 아이콘 (192x192, 512x512)

권장

  • 오프라인 페이지
  • 설치 프롬프트
  • Push Notifications
  • 백그라운드 동기화

테스트

  • Lighthouse PWA 감사
  • 오프라인 테스트
  • 설치 테스트

출력 형식

## PWA Implementation

### Manifest
```json
{ "name": "...", ... }

Service Worker

  • 캐싱 전략: [Cache First/Network First/etc]
  • Workbox 사용: [Yes/No]

Features

  • 오프라인 지원
  • 설치 가능
  • Push Notifications

Lighthouse Score

  • PWA: 100
  • Performance: 90+

---

요청에 맞는 PWA를 구현하세요.
Install via CLI
npx skills add https://github.com/excatt/superclaude-plusplus --skill pwa
Repository Details
star Stars 13
call_split Forks 2
navigation Branch main
article Path SKILL.md
More from Creator