본문으로 이동
메뉴 여닫기
환경 설정 메뉴 여닫기
개인 메뉴 여닫기
로그인하지 않음
지금 편집한다면 당신의 IP 주소가 공개될 수 있습니다.
Progressive Web App, PWA; 프로그레시브 웹 앱
설치, 오프라인 실행, 푸시 알림 같은 앱의 능력을 표준 웹 기술로 갖춘 웹 애플리케이션

프로그레시브 웹 앱(Progressive Web App, PWA)은 HTML, CSS, 자바스크립트로 만든 웹 애플리케이션이 서비스 워커와 웹 앱 매니페스트 같은 브라우저 기능을 이용해 기기에 설치되고, 독립된 창으로 실행되며, 네트워크가 없어도 동작하고, 푸시 알림을 받을 수 있게 만든 것을 가리킨다. 특정 API나 표준 하나의 이름이 아니라, 이런 기능을 갖춘 웹 앱을 부르는 이름이자 설계 방식이다.

"프로그레시브"는 점진적 향상(progressive enhancement)에서 왔다. 구형 브라우저에서는 평범한 웹사이트로 동작하고, 기능을 지원하는 브라우저에서는 앱처럼 동작해야 한다는 뜻이다. URL로 공유하고 검색 엔진에 노출되는 웹의 장점을 유지하면서, 앱 스토어를 거치지 않고 설치할 수 있다는 점이 핵심이다.

2015년 6월 Google Chrome 엔지니어 Alex Russell이 블로그 글 "Progressive Web Apps: Escaping Tabs Without Losing Our Soul"에서 이 개념을 정리했다. 이름은 디자이너 Frances Berriman과 함께 지었는데, Berriman은 처음에 "Progressive Open Web Apps"라고 불렀고 둘이 "Progressive Apps"로 줄였다고 적었다.[1] 이 글이 제시한 특징은 다음 9가지다.

특징 뜻
반응형(Responsive) 휴대폰, 태블릿, 데스크톱 어느 화면에도 맞는다(반응형 웹 디자인)
연결 독립(Connectivity independent) 서비스 워커로 오프라인이나 느린 네트워크에서도 동작한다
앱 같은 상호작용(App-like) 앱 셸(shell)과 콘텐츠를 나누는 구조로 앱처럼 화면을 전환한다
최신 상태(Fresh) 서비스 워커 갱신 과정으로 늘 최신 버전을 받는다
안전(Safe) TLS로 제공해 가로채기와 변조를 막는다
발견 가능(Discoverable) 매니페스트와 서비스 워커 등록으로 검색 엔진과 브라우저가 "앱"임을 알아볼 수 있다
재참여(Re-engageable) 푸시 알림 같은 OS 기능으로 사용자를 다시 부를 수 있다
설치 가능(Installable) 앱 스토어 없이 홈 화면에 추가할 수 있다
연결 가능(Linkable) URL로 공유하고, 설치 없이 바로 열 수 있다

Google은 2016년 Google I/O 등에서 PWA를 적극 홍보했고, 이 때문에 PWA가 Google I/O 2016에서 처음 소개되었다고 설명되기도 한다. 개념과 이름은 그 전해인 2015년에 나왔다.

오프라인 동작이나 설치형 웹 앱이라는 생각 자체는 새롭지 않았다. 2007년 첫 아이폰 발표 때 Apple은 서드파티 앱을 "웹 2.0 표준으로 만든 웹 앱"으로 제공하겠다고 했다가 이듬해 네이티브 SDK와 앱 스토어로 방향을 틀었다. Mozilla의 Firefox OS(2013~2016)는 웹 앱을 네이티브 앱처럼 설치해 쓰는 모바일 OS였다. HTML5의 Application Cache(AppCache)도 오프라인 웹 앱을 목표로 했지만 다루기 어려워 폐기되고 서비스 워커로 대체되었다.

웹 앱 매니페스트

편집 원본 편집

웹 앱 매니페스트(Web App Manifest)는 앱 이름, 아이콘, 시작 URL, 표시 방식, 테마 색 등을 담은 JSON 파일이다. W3C 명세이고, HTML에서 <link rel="manifest">로 연결한다.

{
  "name": "IT위키 리더",
  "short_name": "IT위키",
  "id": "/",
  "start_url": "/?source=pwa",
  "scope": "/",
  "display": "standalone",
  "background_color": "#ffffff",
  "theme_color": "#1a73e8",
  "icons": [
    { "src": "/icons/192.png", "sizes": "192x192", "type": "image/png" },
    { "src": "/icons/512.png", "sizes": "512x512", "type": "image/png" },
    { "src": "/icons/maskable.png", "sizes": "512x512", "type": "image/png", "purpose": "maskable" }
  ]
}

매니페스트의 display 값에 따라 브라우저 UI가 달라진다. fullscreen은 전체 화면, standalone은 주소창 없는 독립 창, minimal-ui는 최소한의 탐색 버튼, browser는 일반 탭이다. 데스크톱 Chromium은 제목 표시줄 영역까지 앱이 그리는 window-controls-overlay도 지원한다. 그 밖에 shortcuts(아이콘 길게 누르기 메뉴), share_target(공유 대상 등록), file_handlers, protocol_handlers 같은 확장 멤버가 있다.

iOS Safari는 매니페스트를 부분적으로만 읽어 왔고, 오랫동안 apple-touch-icon, apple-mobile-web-app-capable 같은 Apple 전용 <meta>·<link> 태그로 아이콘, 전체 화면, 시작 화면을 정했다.

서비스 워커(Service Worker)는 페이지와 별도 스레드에서 도는 자바스크립트 워커로, 자기 범위(scope) 안의 모든 네트워크 요청을 가로채 응답을 직접 만들 수 있는 "프로그래밍 가능한 네트워크 프록시"다. 페이지가 닫혀 있어도 브라우저가 이벤트(푸시 메시지, 백그라운드 동기화)에 맞춰 깨운다. 요청을 가로채는 강력한 기능이라 HTTPS에서만 등록할 수 있다(localhost는 예외). DOM에는 접근할 수 없고, 저장소로는 Cache API와 IndexedDB를 쓴다.

생명주기는 등록(register) → 설치(install) → 활성화(activate) → 제어(fetch 등 이벤트 처리) 순이다. 새 버전이 설치되어도 기존 버전이 제어하는 탭이 모두 닫힐 때까지 대기(waiting) 상태로 있다가 활성화된다. 한 범위에는 한 번에 한 버전만 활성화되므로, 페이지들이 서로 다른 버전의 캐시를 쓰는 일이 생기지 않는다. skipWaiting()과 clients.claim()으로 즉시 교체할 수도 있다.

// app.js : 페이지에서 등록
if ('serviceWorker' in navigator) {
  navigator.serviceWorker.register('/sw.js');
}

// sw.js : 앱 셸을 미리 캐시하고, 캐시 우선으로 응답
const CACHE = 'shell-v3';
const SHELL = ['/', '/app.css', '/app.js', '/offline.html'];

self.addEventListener('install', e => {
  e.waitUntil(caches.open(CACHE).then(c => c.addAll(SHELL)));
});

self.addEventListener('activate', e => {
  e.waitUntil(caches.keys().then(keys =>
    Promise.all(keys.filter(k => k !== CACHE).map(k => caches.delete(k)))));
});

self.addEventListener('fetch', e => {
  e.respondWith(
    caches.match(e.request).then(hit => hit ||
      fetch(e.request).catch(() => caches.match('/offline.html')))
  );
});

HTTP 캐시가 "한 번 받은 것을 추측으로 보관"한다면, 서비스 워커 캐시는 쓰기 전에 미리 받아 두고 필요 없을 때 명시적으로 지울 수 있다. 전략은 보통 다음 가운데서 고른다.

전략 동작 어울리는 자원
캐시 우선(Cache first) 캐시에 있으면 캐시, 없으면 네트워크 앱 셸, 버전 붙은 정적 파일, 글꼴
네트워크 우선(Network first) 네트워크 시도, 실패하면 캐시 자주 바뀌는 HTML, API 응답
Stale-while-revalidate 캐시로 즉시 응답하고 뒤에서 네트워크로 갱신 아바타, 목록처럼 약간 늦어도 되는 데이터
네트워크 전용 / 캐시 전용 한쪽만 사용 결제 요청 / 미리 캐시한 자원

직접 짜기보다 Google의 Workbox 같은 라이브러리로 캐시 전략과 버전 관리를 생성하는 경우가 많다.

앱 셸(App Shell) 모델은 헤더, 메뉴, 레이아웃 같은 UI 뼈대를 서비스 워커로 미리 캐시해 두고, 실행 시 셸을 즉시 그린 뒤 콘텐츠만 네트워크나 저장소에서 채워 넣는 구조다. 네트워크가 느려도 첫 화면이 바로 뜨고, 오프라인이면 셸과 캐시된 콘텐츠로 동작한다.

PWA의 실행 컨텍스트는 언제든 내려갈 수 있으므로 상태는 저장소에 남겨야 한다. 키-값 저장소인 Web Storage(localStorage, sessionStorage)는 동기식이고 워커에서 못 쓰므로, 큰 데이터나 서비스 워커와 공유할 데이터는 IndexedDB, 요청·응답은 Cache API, 파일은 OPFS(Origin Private File System)에 둔다. 브라우저는 저장 공간이 부족하면 출처 단위로 데이터를 지울 수 있다. navigator.storage.persist()로 영구 저장을 요청할 수 있지만 허용 여부는 브라우저가 정한다.

푸시 알림과 백그라운드 기능

편집 원본 편집

웹 푸시는 Push API(서비스 워커가 푸시 메시지를 받음), Notifications API(알림 표시), 그리고 서버가 브라우저 벤더의 푸시 서비스로 메시지를 보내는 Web Push 프로토콜(RFC 8030)과 VAPID 서버 인증(RFC 8292), 메시지 암호화(RFC 8291)로 이루어진다. 사용자가 권한을 허락해야 하고, 탭이 닫혀 있어도 알림이 온다.

// sw.js
self.addEventListener('push', e => {
  const data = e.data ? e.data.json() : { title: '새 소식' };
  e.waitUntil(self.registration.showNotification(data.title, { body: data.body, icon: '/icons/192.png' }));
});
self.addEventListener('notificationclick', e => {
  e.notification.close();
  e.waitUntil(clients.openWindow('/'));
});

그 밖에 앱 아이콘 배지(Badging API), 백그라운드 동기화(Background Sync), 주기적 동기화, 공유(Web Share, Share Target), 파일 시스템 접근 같은 API가 있는데, 대부분 Chromium 계열에만 있거나 브라우저마다 지원이 다르다.

브라우저가 "설치 가능"으로 판단하면 주소창 아이콘이나 메뉴로 설치를 제안한다. Chrome의 현재 기준은 HTTPS, 매니페스트(name 또는 short_name, 192px·512px 아이콘, start_url, display가 fullscreen·standalone·minimal-ui·window-controls-overlay 중 하나, prefer_related_applications가 없거나 false), 그리고 사용자가 한 번 이상 클릭하고 30초 이상 머무른 참여 조건이다.[2] 초기에는 fetch 핸들러를 가진 서비스 워커도 필수였지만 지금 Chrome 기준에서는 빠졌다. 오프라인 동작은 설치 조건이 아니라 품질 요건이 된 셈이다.

Chromium은 beforeinstallprompt 이벤트로 사이트가 자체 "설치" 버튼을 띄우게 해 준다. Safari는 이 이벤트가 없고 사용자가 공유 메뉴의 "홈 화면에 추가"(iOS)나 "Dock에 추가"(macOS)를 직접 골라야 한다. 설치된 PWA는 OS 앱 목록, 작업 표시줄, 앱 전환기에 네이티브 앱처럼 나타난다.

플랫폼별 지원 이력

편집 원본 편집
시기 사건
2015년 Chrome이 서비스 워커를 먼저 출시했다. 6월 Alex Russell이 PWA 개념을 정리했다[1]
2016년 Firefox가 서비스 워커를 지원했다. Google이 Android용 PWA를 적극 홍보했다
2018년 3~4월 Safari 11.1(macOS)과 iOS 11.3이 서비스 워커와 Cache API를 지원하면서 주요 브라우저 모두에 서비스 워커가 갖추어졌다.[3] 같은 해 Edge도 지원했다. 이때 iOS는 푸시 알림, 설치 안내가 없었다
2019년 2월 Chrome 72(Android)가 Trusted Web Activity를 도입해 PWA를 Google Play 앱으로 감쌀 수 있게 되었다[4]
2019년 무렵 데스크톱 Chrome(Windows, macOS, ChromeOS, Linux)과 Edge가 PWA 설치를 지원했다
2020년 12월 Firefox 데스크톱이 실험 기능이던 사이트별 브라우저(SSB) 기능을 제거하며 데스크톱 PWA 지원을 접었다. Android용 Firefox는 계속 지원했다
2023년 3월 iOS·iPadOS 16.4가 홈 화면에 추가한 웹 앱에 한해 웹 푸시를 지원했다. 배지 API, 매니페스트 id, 서드파티 브라우저의 "홈 화면에 추가"도 함께 들어왔다(2월 16일 베타 발표)[5]
2023년 9월 Safari 17(macOS 소노마)이 "Dock에 추가"로 어떤 웹사이트든 macOS 웹 앱으로 만들 수 있게 했다[6]
2024년 2~3월 EU 디지털 시장법(DMA) 대응을 담은 iOS 17.4 베타에서 Apple이 EU 사용자의 홈 화면 웹 앱을 독립 앱이 아니라 Safari로 열리는 바로가기로 격하했다. Apple은 다른 브라우저 엔진을 허용해야 하는 상황에서 보안·개인정보 문제를 이유로 들었다. 개발자들의 반발과 유럽연합 집행위원회의 조사 움직임이 이어지자 3월 1일 이를 철회하고, EU에서도 기존처럼 WebKit 기반 홈 화면 웹 앱을 계속 지원한다고 밝혔다[7]
2025년 9월 Firefox 143이 Windows에서 웹사이트를 작업 표시줄에 고정되는 웹 앱으로 실행하는 기능을 넣었다(Microsoft Store 설치판 제외)[8]

iOS에서는 서드파티 브라우저도 WebKit을 써야 했기 때문에(EU 제외) iOS의 PWA 기능은 사실상 Safari의 지원 범위로 정해진다.

네이티브·하이브리드와 비교

편집 원본 편집
항목 PWA 하이브리드 앱(Cordova, Capacitor 등) 크로스플랫폼 네이티브(React Native, Flutter) 네이티브 앱
코드 웹 코드 하나 웹 코드를 네이티브 WebView 안에서 실행 공용 코드, 네이티브 UI 또는 자체 렌더러 플랫폼별 코드(Swift, Kotlin 등)
배포 URL, 선택적으로 스토어 스토어 스토어 스토어
업데이트 서버 배포 즉시 웹 부분은 가능, 네이티브 부분은 심사 스토어 심사(일부 OTA 가능) 스토어 심사
설치 크기 매우 작음 중간 큼 큼
기기 API 브라우저가 연 것만 플러그인으로 대부분 대부분 전부
검색·링크 검색 엔진 노출, URL 공유 앱 내부라 어려움 어려움 딥 링크 필요
iOS 제약 큼 적음 적음 없음

PWA 쪽 성공 사례로 자주 인용되는 것은 Twitter Lite(2017년, 네이티브 앱 크기의 1~3%), Starbucks 주문 PWA, Pinterest, Flipkart 등이다. 이런 수치는 대부분 기업이 직접 발표하거나 Google이 사례 연구로 소개한 것이다. PWA 홍보 자료에서는 comScore 2017년 미국 모바일 앱 보고서를 근거로 "사용자 절반가량은 한 달에 새 앱을 하나도 설치하지 않는다"는 점과 "모바일 사용 시간의 대부분은 앱에서 쓴다"는 점이 함께 제시되었다. 웹은 도달 범위가, 앱은 사용 시간이 강점이라 둘을 합치자는 논리다.

PWA는 스토어가 필요 없지만, 사용자가 스토어에서 앱을 찾는 습관 때문에 스토어에 올리기도 한다.

스토어 방식
Google Play Trusted Web Activity(TWA). Custom Tabs 프로토콜 위에서 PWA를 브라우저 UI 없이 전체 화면으로 띄우는 Android 앱이다. Digital Asset Links(/.well-known/assetlinks.json)로 앱과 사이트가 같은 개발자 소유임을 증명해야 하고, 사이트가 설치 기준을 만족해야 한다. Bubblewrap(Node.js CLI)이나 PWABuilder로 프로젝트를 생성한다[4]
Microsoft Store Windows는 PWA를 스토어 앱으로 직접 받아 준다. PWABuilder로 패키지를 만들고, Bing 색인으로 찾아낸 일부 PWA를 스토어에 자동 등록한 적도 있다
Samsung Galaxy Store PWA 등록을 지원한다
Apple App Store PWA 자체는 올릴 수 없다. WKWebView로 감싼 앱을 올릴 수는 있지만 심사 지침 4.2(최소 기능)는 웹사이트를 다시 포장한 수준의 앱을 거절 사유로 든다
  • iOS의 제약: 설치 안내 이벤트가 없어 사용자가 직접 공유 메뉴에서 추가해야 하고, 웹 푸시도 홈 화면에 추가한 뒤에만 된다. Safari는 2020년부터 사용자가 7일 동안 방문하지 않은 사이트의 스크립트 기록 저장소를 지우는데, 홈 화면 웹 앱은 이 규칙에서 빠진다.[9]
  • 기기 API: Web Bluetooth, WebUSB, Web NFC, Web Serial, 파일 시스템 접근 같은 API는 Chromium 계열에만 있고, Safari와 Firefox는 보안·개인정보를 이유로 구현하지 않는다. 연락처, 백그라운드 위치, 전화 기능 등은 웹에 아예 없다.
  • 백그라운드 실행: 서비스 워커는 이벤트가 있을 때만 잠깐 깨어나고 브라우저가 수명을 제한한다. 음악 재생 외의 상시 백그라운드 작업은 어렵다.
  • 저장 공간: 저장 공간 압박 시 데이터가 지워질 수 있고 한도가 브라우저마다 다르다.
  • 발견성과 신뢰: 스토어 순위, 리뷰, 결제 체계를 쓰지 못한다. 사용자가 "앱 설치"를 스토어와 연결해 생각하는 경향도 크다.
  • 캐시 관리의 어려움: 서비스 워커를 잘못 만들면 옛 버전이 계속 뜨거나 오류 페이지가 캐시되어 빠져나올 수 없는 문제가 생긴다. 서비스 워커 파일 자체는 HTTP 캐시를 짧게(또는 no-cache) 두어야 갱신이 전달된다.
  • 보안: 서비스 워커는 출처 전체의 요청을 가로채므로, 사이트에 크로스사이트 스크립트 취약점이 있거나 사용자 업로드 파일을 같은 출처에서 서비스하면 공격자가 악성 서비스 워커를 심어 지속적으로 응답을 조작할 위험이 있다. HTTPS 필수, 스크립트 경로에 따른 범위 제한, Service-Worker-Allowed 헤더 같은 제한이 이 때문에 있다.

개발·검사 도구

편집 원본 편집
  • Lighthouse: Google의 오픈 소스 웹 품질 검사 도구. 예전에는 PWA 항목 점수가 있었지만 Chrome의 설치 기준이 바뀐 것에 맞춰 2024년 5월 Lighthouse 12에서 PWA 범주가 빠졌다.[10]
  • Chrome 개발자 도구의 Application 패널: 매니페스트, 서비스 워커 상태, 캐시 저장소를 확인하고 오프라인을 흉내 낼 수 있다.
  • Workbox(서비스 워커 생성·캐시 전략 라이브러리), PWABuilder(스토어 패키지 생성).