우당탕탕 PWA 도입하기
PWA
PWA(Progressive Web App)은 구글 I/O 2016에서 발표한 기술입니다. 웹과 네이티브 앱의 기능 중 이점만을 가져 올 수 있도록 수 많은 기술과 패턴을 사용해 개발된 웹 앱을 총칭합니다.
웹 앱과 네이티브 앱이 가지는 장점만을 취하기 위해서 PWA는 새로운 기술 (Service Worker; 후술) 을 같이 사용합니다. 발견의 용이성을 위해서, PWA는 Android나 iOS 운영체제에 종속되지 않고 양쪽 플랫폼에서 동시에 실행될 수 있는 웹 기반 플랫폼을 사용합니다. 따라서 플랫폼에 관계 없이 어떤 운영체제에서도 실행될 수 있는 하나의 앱만을 제작해도 됩니다.
웹 기반 플랫폼의 한계를 벗어나, PWA는 네이티브 앱처럼 동작하여 브라우저 레벨에서 접근할 수 없었던 시스템 하드웨어나 소프트웨어에도 접근할 수 있습니다. PWA의 목표 중 하나는 성능이 뛰어날 경우 유저가 웹 기반 앱을 사용하고 있는지, 네이티브 앱을 사용하는 것인지 헷갈릴 정도의 사용자 경험을 제공하는 것입니다. 잘 설계된 PWA란 웹 앱과 네이티브 앱의 장점을 결합한 UX를 제공할 수 있습니다.
PWA 도입 배경
- 최초 프로젝트를 기획할 당시에 react와 react-native 사이에서 많은 고민을 하였습니다. 왜냐하면 저희가 구현하고자 하는 프로젝트의 사용자 대부분은 데스크탑의 web이 아닌 모바일로 더욱 많이 사용자가 유입 될 것이라고 생각했기 때문입니다. 하지만 안드로이드, ios 환경에 대한 이해가 없는 상황에서 native가 react와 유사하다는 이유 만으로 도입하기에는 에러 해결이나 기능 최적화에서 어려움을 많이 겪을 것이라고 생각했습니다.
- 위와 같은 이유로 PWA를 통해 앱과 같이 동작하도록만 하면 어떨까라는 생각을 하게 되었습니다. 인프런 과 핀터레스트 또한 PWA를 제공한다는 점에서 기능이 복잡하지 않은 간단한 웹 프로젝트이기에 PWA로 충분하다는 생각을 하게 되었습니다.
- PWA의 장점 또한 개발비용이 저렴하고 코드베이스가 적기 때문에 간단하게 도입할 수 있을 것이라는 기대감으로 프로젝트에 도입하였습니다.
Service Worker
Service Worker (앞으로 SW로 통칭) 란 브라우저가 백그라운드에서 실행하는 스크립트입니다. 이 스크립트는 웹 페이지나 유저 인터랙션과 완전히 분리되어 실행됩니다. SW는 다음과 같은 특성을 가지고 있습니다.
- SW는 자바스크립트 기반의 worker로서, DOM에 직접 접근할 수 없습니다.
- 대신, postMessage 인터페이스를 이용해 페이지에 있는 메인 스레드와 통신할 수 있습니다.
- SW는 프로그래밍이 가능한 네트워크 프록시로, 페이지에서 보내는 네트워크 요청들을 어떻게 처리할지 커스텀하게 프로그래밍 할 수 있습니다.
- SW는 쓰이지 않을때 종료되고, 필요할 때 재시작되기 때문에, SW의
onfetch혹은onmessage핸들러에서 유추할 수 있는 global state 는 언제나 최신 상태로 유지된다고 확신할 수 없습니다. SW에서 계속해서 유지하며 사용해야 하는 정보를 담기 위해서는 IndexedDB API 를 사용할 수 있습니다.
Service Worker 를 설치하고 실행하기 위해서는 첫 번째로 브라우저가 이를 지원해야 합니다. SW는 Firefox, Chrome, Opera 에서 지원되고 있으며 Chrome 기반의 웹 브라우저인 Microsoft Edge와 Naver Whale 에서도 가능합니다. Safari 에서는 일부 기능이 지원됩니다.
추가로 SW는 HTTPS 에서만 실행이 가능하나 개발을 위해 localhost (<http://localhost>) 에서는 이 조건이 생략됩니다.
마지막으로 manifest.json 의 유무가 있습니다. Web app manifest는 JSON 파일로 PWA에 대한 정보와 현재 웹 사이트가 유저의 데스크탑이나 모바일 장치에 어떻게 설치되어야 하는지에 대한 정보를 저장한 파일입니다. 이 파일은 앱 이름, 아이콘, 그리고 앱의 URL 정보 등을 포함하고 있습니다.
React PWA 도입하기
- vite 번들러 환경에서 진행을 하였습니다.
1. 아이콘
https://favicomatic.com/ 해당 사이트에서 필요로하는 favicon.ico 를 각 크기 별로 모두 생성할 수 있습니다.
2. mainfest.json 작성
/public/mainfest.json파일을 작성합니다.
{
"name": "Share Petment",
"short_name": "Share Petment",
"start_url": "/",
"display": "standalone",
"background_color": "#ffffff",
"theme_color": "#485EEE",
"icons": [
{
"src": "/favicon-48x48.png",
"sizes": "48x48",
"type": "image/png",
"purpose": "any maskable"
},
{
"src": "/apple-touch-icon-72x72.png",
"sizes": "72x72",
"type": "image/png",
"purpose": "any maskable"
},
{
"src": "/favicon-96x96.png",
"sizes": "96x96",
"type": "image/png",
"purpose": "any maskable"
},
{
"src": "/mstile-144x144.png",
"sizes": "144x144",
"type": "image/png",
"purpose": "any maskable"
},
{
"src": "/apple-touch-icon-152x152.png",
"sizes": "152x152",
"type": "image/png",
"purpose": "any maskable"
},
{
"src": "/icon-512x512.png",
"sizes": "512x512",
"type": "image/png"
}
]
}- short_name: 아이콘 아래에 표시될 이름
- name: 설치 배너에 표시되는 이름
- start_url: 스플래시 화면 다음에 시작 url
- display: 웹앱 화면 설정
3. service worker
- 오프라인에서 작업하기 위한 코드로 sw.js를 작성합니다.
/* eslint-disable @typescript-eslint/no-explicit-any */
// install event
self.addEventListener('install', () => {
console.log('[Service Worker] installed');
});
// activate event
self.addEventListener('activate', () => {
console.log('[Service Worker] actived');
});
// fetch event
self.addEventListener('fetch', () => {
console.log('[Service Worker] fetched resource ');
});/public/index.html에 script 태그로 sw,js 파일이 동작하도록 설정합니다. 추가적으로manifest.json파일도 html과 연결합니다.
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="manifest" href="manifest.json" />
<title>Share Petment</title>
</head>
<body>
<div id="root" class="relative"></div>
<script type="module" src="/src/main.tsx"></script>
<script>
if ('serviceWorker' in navigator) {
// Register a service worker hosted at the root of the
// site using the default scope.
navigator.serviceWorker
.register('./sw.js')
.then(registration => {
console.log('Service worker registration succeeded:');
})
.catch(err => {
console.log('Service worker registration failed:', err);
});
} else {
console.log('Service workers are not supported.');
}
</script>
</body>
</html>-
아래와 같이 정상적으로 동작되는 것을 확인할 수 있습니다.

-
추가적으로 lighthouse를 활용해서 PWA 지원 여부를 확인할 수 있습니다.

Reference
Progressive Web App (1) PWA란 무엇일까? React 프로젝트에 PWA 적용하기 프론트엔드 개발자가 PWA 알아야 하는 이유 | 요즘IT 프로그레시브 웹 앱(PWA)이란 무엇이며, 왜 필요한가? | 요즘IT
- - PWA
- - PWA 도입 배경
- - Service Worker
- - React PWA 도입하기
- 2. mainfest.json 작성
- - 3. service worker