PWA(Progressive Web Apps)의 존재를 뒤늦게 알고 열심히 공부해 봤습니다. 지금까지 앱 개발은 비싸고 어렵다는 선입견 때문에 내 영역 밖이라고 생각하고 있었는데 어느날 웹앱? 이란 것을 알게 되면서 내가 운영하고 있는 URL.KR 부터 설치해 보기로 맘 먹고 오늘 설치 완료 했습니다. 그래서 이번 포스팅은 잊어버리기 전에 내용을 정리하는 기록형 포스팅 입니다. 물론 제가 전문가가 아니기 때문에 어떤 질문에 답을 할 수 있는 상황은 아니지만 저처럼 설치를 망설이거나 설치 중이신 분들에게 조금이라도 도움이 되었으면 하는 바램 입니다.

PWA란? 개념과 장/단점
PWA(Progressive Web Apps)는 웹 기술과 네이티브 앱의 장점을 결합한 웹 앱으로, 모바일 기기에서 네이티브 앱과 유사한 사용자 경험을 제공하가 때문에 앱 개발이 망설여 진다면 한번 설치해 보세요.
PWA의 장점
PWA는 앱 스토어를 거치지 않고 웹 브라우저에서 바로 실행할 수 있어, 별도의 설치 과정 없이 손쉽게 접근할 수 있습니다. 또한, 앱 스토어를 거치지 않기 때문에 업데이트가 자동으로 이루어져 사용자는 항상 최신 버전을 이용할 수 있습니다.
추가적으로, PWA는 오프라인에서도 정상적으로 작동할 수 있어 네트워크 연결이 불안정한 환경에서도 안정적인 사용자 경험을 제공합니다. 더불어, 웹 기반 기술을 활용하기 때문에 검색 엔진 최적화(SEO)에 유리하며, 검색을 통해 더 많은 사용자에게 노출될 가능성이 높아집니다.
마지막으로, 한 번의 개발만으로 안드로이드와 iOS 모두에서 호환되기 때문에 개발 비용과 유지보수 부담을 줄일 수 있다는 것도 큰 장점 중 하나입니다.
PWA의 단점
PWA는 웹 브라우저를 기반으로 동작하기 때문에 특정 브라우저에 의존적인 구조를 가지며, 모든 브라우저에서 동일한 기능을 지원하지 않을 수도 있습니다. 특히, 일부 구형 브라우저나 특정 브라우저 환경에서는 PWA의 일부 기능이 제한될 가능성이 있습니다.
또한, 네이티브 앱과 비교했을 때 완벽한 사용자 경험을 제공하기 어려운 부분이 있습니다. 예를 들어, 고성능 그래픽 처리나 일부 하드웨어 기능(블루투스, NFC 등)의 활용이 제한될 수 있으며, 네이티브 앱과 동일한 수준의 성능을 보장하기 어려울 수도 있습니다.
더불어, PWA는 캐싱을 활용해 오프라인에서도 작동할 수 있지만, 캐싱이 필요한 데이터를 효율적으로 관리하는 것이 쉽지 않으며, 개발자가 이를 신중하게 설계해야 하는 부담이 있습니다. 이러한 한계로 인해, 네이티브 앱과 동일한 수준의 기능을 요구하는 서비스에서는 PWA가 다소 부족할 수 있습니다.
PWA 구현 단계별 가이드
HTTPS 설정 확인
PWA(Progressive Web App)는 보안이 중요한 웹 애플리케이션이므로, 반드시 HTTPS(SSL/TLS 암호화) 환경에서 제공되어야 합니다. 이는 사용자 데이터 보호뿐만 아니라, 서비스 워커(Service Worker) 등의 주요 기능이 정상적으로 작동하기 위한 필수 조건입니다.
HTTPS를 설정하는 방법에는 여러 가지가 있으며, 대표적으로 무료 SSL 인증서를 제공하는 Let’s Encrypt를 활용할 수 있습니다. 또한, 일부 호스팅 서비스나 클라우드 플랫폼(AWS, Firebase, Vercel 등)에서도 SSL 인증서를 기본적으로 제공하므로, 이를 이용하면 보다 쉽게 보안 설정을 완료할 수 있습니다.
만약 기존에 HTTP 환경에서 운영되고 있다면, HTTPS로 전환 후 리디렉션 설정(301 Redirect) 을 적용하여 모든 요청이 안전한 HTTPS로 자동 연결되도록 구성하는 것이 중요합니다.
웹 앱 매니페스트(manifest.json) 생성
{
"name": "사이트 전체 이름",
"short_name": "짧은 이름",
"description": "사이트 설명",
"start_url": "/",
"display": "standalone",
"background_color": "#ffffff",
"theme_color": "#색상코드",
"icons": [
{
"src": "/images/icon-192x192.png",
"sizes": "192x192",
"type": "image/png",
"purpose": "any maskable"
},
{
"src": "/images/icon-512x512.png",
"sizes": "512x512",
"type": "image/png",
"purpose": "any maskable"
}
]
}
아이콘 준비
- 최소 두 가지 크기의 아이콘 준비: 192x192px, 512x512px
- PNG 형식 권장 (투명배경)
서비스 워커(service-worker.js) 구현
// 캐시 이름과 버전 정의
const CACHE_NAME = '사이트이름-v1';
// 캐시할 파일 목록
const urlsToCache = [
'/',
'/index.html',
'/css/main.css',
'/js/main.js',
'/images/icon-192x192.png',
'/images/icon-512x512.png',
// 필요한 리소스 추가
];
// 설치 이벤트
self.addEventListener('install', (event) => {
event.waitUntil(
caches.open(CACHE_NAME)
.then((cache) => {
console.log('캐시에 리소스 저장 중');
return cache.addAll(urlsToCache);
})
);
});
// 활성화 이벤트
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.addEventListener('fetch', (event) => {
event.respondWith(
fetch(event.request)
.then((response) => {
// 유효한 응답인 경우에만 캐시에 저장
if (response && response.status === 200) {
const responseToCache = response.clone();
caches.open(CACHE_NAME)
.then((cache) => {
cache.put(event.request, responseToCache);
});
}
return response;
})
.catch(() => {
// 네트워크 요청 실패시 캐시된 응답 반환
return caches.match(event.request);
})
);
});
HTML 페이지 수정
모든 HTML 페이지의 <head> 섹션에 다음 코드 추가:
<!-- PWA 메타 태그 -->
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="theme-color" content="#색상코드">
<meta name="mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black">
<meta name="description" content="사이트 설명">
<!-- 매니페스트 링크 -->
<link rel="manifest" href="/manifest.json">
<!-- 아이콘 설정 -->
<link rel="icon" href="/images/icon-192x192.png">
<link rel="apple-touch-icon" href="/images/icon-192x192.png">
서비스 워커 등록 스크립트 추가
모든 HTML 페이지의 </body> 태그 바로 앞에 다음 스크립트 추가:
<script>
// 서비스 워커 등록
if ('serviceWorker' in navigator) {
window.addEventListener('load', () => {
navigator.serviceWorker.register('/service-worker.js')
.then(registration => {
console.log('서비스 워커 등록 성공:', registration.scope);
})
.catch(error => {
console.error('서비스 워커 등록 실패:', error);
});
});
}
// PWA 설치 관련 처리
let deferredPrompt;
const PWA_INSTALLED_KEY = '사이트이름_pwa_installed';
const isInstalled = localStorage.getItem(PWA_INSTALLED_KEY) === 'true';
// 설치 완료 이벤트
window.addEventListener('appinstalled', (evt) => {
localStorage.setItem(PWA_INSTALLED_KEY, 'true');
console.log('앱이 성공적으로 설치되었습니다!');
});
// 설치 프롬프트 캡처
window.addEventListener('beforeinstallprompt', (event) => {
// 이미 설치된 경우 프롬프트 방지
if (isInstalled) {
event.preventDefault();
return false;
}
// 프롬프트 이벤트 저장
deferredPrompt = event;
console.log('설치 프롬프트가 준비되었습니다');
});
</script>
(선택 사항) 수동 설치 버튼 추가
사용자가 설치를 직접 시작할 수 있는 버튼을 추가하고 싶다면:
<button id="installPWA" class="btn btn-primary">앱 설치하기</button>
<script>
const installButton = document.getElementById('installPWA');
// 설치 버튼 초기 상태 설정 (기본적으로 숨김)
installButton.style.display = 'none';
// 설치 프롬프트가 있을 때 버튼 표시
window.addEventListener('beforeinstallprompt', (e) => {
e.preventDefault();
deferredPrompt = e;
installButton.style.display = 'block';
});
// 설치 버튼 클릭 이벤트
installButton.addEventListener('click', async () => {
if (!deferredPrompt) {
return;
}
deferredPrompt.prompt();
const { outcome } = await deferredPrompt.userChoice;
if (outcome === 'accepted') {
localStorage.setItem(PWA_INSTALLED_KEY, 'true');
installButton.style.display = 'none';
}
deferredPrompt = null;
});
</script>
반드시 수정해야 할 항목들
- manifest.json 파일:
- name: 사이트의 전체 이름으로 변경
- short_name: 홈 화면에 표시될 짧은 이름으로 변경
- description: 사이트에 맞는 설명으로 변경
- theme_color: 사이트 테마 색상에 맞게 변경
- icons: 사이트 아이콘 파일의 경로로 변경
- service-worker.js 파일:
- CACHE_NAME: 각 사이트의 이름을 포함한 고유한 캐시 이름으로 변경 (예: 'mysitename-v1')
- urlsToCache: 사이트에서 캐싱할 파일 목록을 실제 사이트의 중요 리소스 경로로 변경
- 필요에 따라 캐싱 전략 수정 (예: API 호출이 많은 사이트는 다른 전략이 필요할 수 있음)
- HTML의 메타 태그:
- meta name="theme-color": 사이트 테마 색상값으로 변경
- meta name="description": 사이트 설명으로 변경
- 아이콘 경로: 사이트 아이콘 파일의 실제 경로로 변경
- 자바스크립트 변수:
- PWA_INSTALLED_KEY: 사이트 이름을 포함한 고유 키로 변경 (예: 'mysitename_pwa_installed')
마무리
제가 운영하고 있는 단축 URL.KR 사이트에 조금 전 설치 완료 했습니다. 모바일에서 접속 시 설치 안내 나오는 것 확인했고 안드로이드 삼성폰 바탕화면에 설치 테스트까지 성공했습니다. 단점에서도 언급했듯이 모든 브라우저나 모든 기기에서 설치가 원할하게 되는게 아니기 때문에 앱 설치가 필수인 사이트 용으로는 권장하지 않습니다. 예전 앱이란 개념이 없을 때 즐겨찾기 하는 듯 가볍게 접근하면 될 것 같습니다.