개요
기존에 하려던 프로젝트가 빠그러지게 되고... 새로운 프로젝트를 구해서 하고있다! 이번에 하려는 것은 PWA를 통해 모바일 화면에서 앱처럼 사용할 수 있는 웹이다!
React 라이브러리를 쓸 것이고 하나하나 차근차근 정리해가면서 완성시켜보도록 하자!
1. PWA란?
PWA(Progressive Web APP)는 웹 앱을 네이티브 앱처럼 사용할 수 있도록 만들어주는 기술이다.
즉, 웹 앱인데 설치도 할 수 있고 푸시 알림도 받을 수 있고 앱처럼 동작하는 웹사이트이다.
1-1. 특징
1) 설치 가능
-> 사용자가 웹 사이트를 모바일이나 데스크탑 홈 화면에 앱치로 설치할 수 있다.
-> 설치했다면 브라우저 UI 없이 독립적인 실행 형태로 동작한다.
2) 오프라인 사용
-> Service Worker라는 것을 활용해서 캐시된 리소스를 사용해 인터넷이 없어도 일부 기능을 사용할 수 있다.
3) 푸시 알림 지원
-> 네이티브 앱처럼 푸시 알림 전송이 가능하다.
4) 반응형 디자인
-> 모바일, 태블릿, 데스크탑 등 다양한 디바이스에서 최적화된 UI를 제공한다.
5) HTTPS 필수
-> 보안을 위해 반드시 HTTPS에서만 동작하는데 이는 Service Worker가 HTTPS 환경에서만 작동하기 때문이다.
6) 빠른 로딩
-> 리소스를 캐시에 저장해두고 필요할 때 불러오기 때문에 빠르게 로드된다.
2. React + PWA 적용
2-1. 프로젝트 생성
Create React App으로 시작하는 경우에 기본적인 PWA 설정이 포함되어 있다.
npx create-react-app my-pwa-app --template cra-template-pwa
: cra-template-pwa
-> 템플릿을 사용한다면 PWA 기능이 사전에 구성되어 있다.
2-2. manifest.json 설정
-> public/manifest.json 파일에서 앱의 이름, 아이콘, 테마 등을 설정해 앱처럼 보이게 만들 수 있다.
{
"short_name": "용지",
"name": "Create React App Sample",
"icons": [
{
"src": "favicon.ico",
"sizes": "64x64 32x32 24x24 16x16",
"type": "image/x-icon"
},
{
"src": "logo192.png",
"type": "image/png",
"sizes": "192x192"
},
{
"src": "logo512.png",
"type": "image/png",
"sizes": "512x512"
}
],
"start_url": ".",
"display": "standalone",
"theme_color": "#000000",
"background_color": "#ffffff"
}
: short_name
-> 홈 화면에 표시될 앱 이름
: icons
-> 설치 시 사용할 아이콘
: start_url
-> 앱 시작 URL
2-3. service-worker.js 설정
Service Worker는 웹 브라우저와 웹 서버 사이에서 중간자 역할을 한다.
-> 백그라운드에서 작동
-> 오프라인 지원(캐싱)
-> 푸시 알림
-> 백그라운드 동기화
-> 네트워크 요청 가로채기 및 제어
와 같은 기능을 제공한다.
프로젝트를 생성하면 안에 코드가 미리 적혀있는데, 하나씩 살펴보자.
일단 전체 코드는 아래와 같다.
import { clientsClaim } from 'workbox-core';
import { ExpirationPlugin } from 'workbox-expiration';
import { precacheAndRoute, createHandlerBoundToURL } from 'workbox-precaching';
import { registerRoute } from 'workbox-routing';
import { StaleWhileRevalidate } from 'workbox-strategies';
clientsClaim();
precacheAndRoute(self.__WB_MANIFEST);
const fileExtensionRegexp = new RegExp('/[^/?]+\\.[^/]+$');
registerRoute(
({ request, url }) => {
if (request.mode !== 'navigate') {
return false;
}
if (url.pathname.startsWith('/_')) {
return false;
}
if (url.pathname.match(fileExtensionRegexp)) {
return false;
}
return true;
},
createHandlerBoundToURL(process.env.PUBLIC_URL + '/index.html')
);
registerRoute(
({ url }) => url.origin === self.location.origin && url.pathname.endsWith('.png'),
new StaleWhileRevalidate({
cacheName: 'images',
plugins: [
new ExpirationPlugin({ maxEntries: 50 }),
],
})
);
self.addEventListener('message', (event) => {
if (event.data && event.data.type === 'SKIP_WAITING') {
self.skipWaiting();
}
});
-> Workbox라는 Service Worker를 쉽게 설정할 수 있게 도와주는 라이브러리가 사용되어 있다.
import { ExpirationPlugin } from 'workbox-expiration';
import { precacheAndRoute, createHandlerBoundToURL } from 'workbox-precaching';
import { registerRoute } from 'workbox-routing';
import { StaleWhileRevalidate } from 'workbox-strategies';
: precacheAndRoute
-> 앱의 빌드 결과물을 미리 캐시
: registerRouter
-> 요청 경로에 따라 캐싱 전략을 다르게 설정한다.
: StaleWhileRevalidate
-> 캐시된 리소스를 먼저 보여주고, 그 뒤에 네트워크에서 새로 받아와 업데이트한다.
: ExpirationPlugion
-> 캐시된 항목을 일정 수 이상이 되면 삭제한다.
clientsClaim();
-> 서비스 워커가 활성화되자마자 클라이언트를 제어하게 한다.
precacheAndRoute(self.__WB_MANIFEST);
-> 자원들을 캐시에 등록하고, 라우팅까지 처리한다.
: self.__WB_MANIFEST
-> 앱이 빌드될 때 자동 생성된 정적 자원 목록(JS, CSS)
const fileExtensionRegexp = new RegExp('/[^/?]+\\.[^/]+$');
registerRoute(
({ request, url }) => {
if (request.mode !== 'navigate') return false;
if (url.pathname.startsWith('/_')) return false;
if (url.pathname.match(fileExtensionRegexp)) return false;
return true;
},
createHandlerBoundToURL(process.env.PUBLIC_URL + '/index.html')
);
-> SPA처럼 동작하게 하기 위해, 모든 내비게이션 요청을 index.html로 리다이렉트한다.
-> 단, 정규식을 통해 리소스 요청이나 내부 API 경로의 경우 제외한다.
registerRoute(
({ url }) => url.origin === self.location.origin && url.pathname.endsWith('.png'),
new StaleWhileRevalidate({
cacheName: 'images',
plugins: [
new ExpirationPlugin({ maxEntries: 50 }),
],
})
);
-> .png 이미지 요청을 캐시한다.
-> 캐시가 50개 이상이면 오래된 항목부터 제거한다.
: Stale-While-Revalidate 전략
-> 먼저 캐시된 이미지를 보여주고, 뒤에서 새로 받아와 업데이트한다.
self.addEventListener('message', (event) => {
if (event.data && event.data.type === 'SKIP_WAITING') {
self.skipWaiting();
}
});
-> 서비스 워커가 업데이트 될 때, 강제로 waiting 상태를 넘어서 즉시 활성화되게 한다.
2-4. index.js에서 서비스 워커 등록
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import * as serviceWorkerRegistration from './serviceWorkerRegistration';
import reportWebVitals from './reportWebVitals';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<React.StrictMode>
<App />
</React.StrictMode>
);
serviceWorkerRegistration.register();
reportWebVitals();
2-5. 실행
HTTPS가 아니더라도 localhost에도 PWA를 실행할 수 있기 때문에 npm start로 실행하면 끝!
이제 프로젝트 개발하면서 정리할 부분은 계속 정리해야지
'Web > React 적용' 카테고리의 다른 글
[React 적용] Vite로 마이그레이션 (0) | 2025.03.25 |
---|---|
[React 적용] create-react-app으로 첫 프로젝트 생성 (1) | 2025.02.05 |