[FE] 내가 만든 웹이 앱으로?? PWA Progressive Web App을 만들어보자!
[FE] 내가 만든 웹이 앱으로?? PWA Progressive Web App을 만들어보자!
웹을 만드는 기술로 앱과 유사한 사용자 경험을 제공할 수 있다면? 더욱 다양한 유형의 사용자를 만족시킬 수 있을 것이다.
React Native인가? 아니다.
바로, PWA(Progressive Web App)이다.
PWA (Progressive Web App)
PWA를 왜 사용하는 걸까?
먼저, PWA는 브라우저에서 동작하기 때문에 다양한 앱스토어에 출시하기 위해 별도의 프로세스를 거치지 않아도 된다. 그렇다고 앱스토어에 출시할 수 없는 것은 아니다. Google Play Store를 비롯한, Microsoft Store나 Samsung Galaxy Store 등 다양한 앱스토어에서 PWA를 지원한다.
일반적인 웹 기술을 사용하여 만들 수 있는 것도 엄청난 장점이다. 일반적으로 제작한 반응형 웹 어플리케이션을 그대로 PWA로 만들 수 있다. 별도의 특별한 기술(ex. kotlin, java, swift)을 사용하여 모바일 어플리케이션을 따로 제작하지 않아도 된다.
오프라인에서 동작하고, 푸시알림이 가능하다는 것도 빼놓을 수 없다. 일반적인 네이티브 어플리케이션은 오프라인에서 동작하지 않지만, PWA는 가능하다. 오프라인에서 동작한다는 것을 헷갈리면 안 되는 게, 완전한 모든 기능이 동작하는 것이 아니라 오프라인 상태에서는 캐시 된 데이터를 사용하고 네트워크에 연결되면 새로운 데이터를 불러와 업데이트할 수 있다는 것이다.
일반적인 웹 어플리케이션은 푸시 알림을 지원하지 않지만, PWA는 가능하다. 푸시 알림 뿐만 아니라 카메라나 마이크 같은 기능도 사용할 수 있다. 또한, PWA는 네이티브 어플리케이션과는 달리 사용자가 어플리케이션을 업데이트할 필요가 없다.
스마트폰을 사용하는 사람들이 점점 많아지고, 그 연령대가 점점 넓어지면서 기업들은 웹 어플리케이션만 보고 있을 수는 없게 되었다. PWA가 완전히 네이티브 어플리케이션을 대체하는 것은 불가능하다. 하지만, 최소한의 기술적인 리소스를 갖고 최대한의 퍼포먼스를 발휘하기 위해서는 PWA만 한 게 없다.
PWA는 어떻게 동작할까?
PWA의 핵심 원리는 App shell과, Service Worker이다.
app shell은 UI와 어플리케이션 로직을 분리하여 웹 어플리케이션의 성능을 향상하는 방법이다. 일반적으로 html, css, js파일로 구성되며 어플리케이션의 레이아웃과 스타일을 정의한다. 사용자에게 최소한의 마크업을 빠르게 전달할 수 있다.
SSR은 초기 로딩은 빠르지만, 페이지간 이동에서 모든 것을 매 번 다운로드 해야한다. CSR은 페이지간 이동은 빠르지만 초기 로딩 시 많은 다운로드와 추가 렌더링이 필요하다.
SSR과 CSR을 혼합하여 서버에서 웹 서비스를 렌더링하고 콘텐츠를 캐싱한다. 이후 클라이언트에서 필요한 페이지를 렌더링 한다. 이 접근법이 app shell이다.
그럼 hydration과 같은 개념이 아닌가? 할 수 있는데, hydration은 서버에서 불러온 마크업을 상호작용할 수 있는 형태로 업데이트하는 것이고, app shell은 초기 어플리케이션의 구조를 정의하는 것이다.
app shell을 사용하여 브라우저에서 어플리케이션을 지원하지 않을 때에도 푸시 알림을 보내고, 홈 화면에 어플리케이션을 추가할 수 있다.
service worker는 PWA가 오프라인에서 작동할 수 있도록 도와준다. service worker는 브라우저와 네트워크 사이의 가상 프록시이다. 이를 통해 웹 사이트를 캐싱하고 사용자가 오프라인에서도 웹 서비스를 사용할 수 있도록 한다.
API는 non blocking이고, service worker에게 작업 내용을 제공한다. promise 기반 접근 방식으로 네트워크에 연결되어 준비가 될 때마다 데이터 결과를 받을 수 있다.
app shell과 service worker를 적절히 결합하여 PWA는 오프라인에서도 콘텐츠를 로딩할 수 있는 것이다.
이제 PWA를 만들어보자.
PWA를 어떻게 만들까?
PWA를 만들기 위해서는 먼저, 반응형으로 만든 웹이 있어야 한다.
나의 경우에는 nextjs app router를 사용하여 라이브러리로 쉽게 만들 수 있었다.
아래 명령어로 패키지를 다운로드한다.
npm install next-pwa
npm install --save next-compose-plugins
nextjs.config.js를 수정해 준다.
나의 경우에는 eslint disable 주석과, dev or prod를 구별하기 위한 변수가 필요했다.
/* eslint-disable import/order */
/* eslint-disable @typescript-eslint/no-var-requires */
/** @type {import('next').NextConfig} */
const isDevelopment = process.env.NODE_ENV === 'development';
const runtimeCaching = require('next-pwa/cache');
const withPWA = require('next-pwa')({
dest: 'public',
register: true,
skipWaiting: true,
runtimeCaching,
disable: isDevelopment,
});
const nextConfig = withPWA({
images: {
domains: ['github.com', 'notion.site'],
},
});
module.exports = nextConfig;
manifest를 만들어준다. manifest는 웹 어플리케이션의 metadata를 정의하는 json 파일이다.
아래는 temp manifest 파일이다. 이 manifest.json파일을 수정하여 public 디렉토리에 삽입하자.
{
"name": "My Temporary Web App",
"short_name": "TempApp",
"start_url": "/",
"display": "standalone",
"theme_color": "#007bff",
"background_color": "#ffffff",
"prefer_related_applications": false,
"icons": [
{
"src": "icons/icon-192x192.png",
"type": "image/png",
"sizes": "192x192"
},
{
"src": "icons/icon-512x512.png",
"type": "image/png",
"sizes": "512x512"
},
]
}
layout.tsx에 metadata를 설정한다.
import type { Metadata } from 'next';
export const metadata: Metadata = {
title: 'EveryGSM',
description: '교내 프로젝트를 하나로 연결해주는 서비스.',
generator: 'Next.js',
manifest: '/manifest.json',
keywords: [
'nextjs',
'nextjs13',
'every',
'everygsm',
'gsm',
'광주소프트웨어마이스터고등학교',
'GSM',
],
themeColor: [{ media: '(prefers-color-scheme: dark)', color: '#E23C96' }],
metadataBase: new URL('https://every.hellogsm.kr'),
viewport:
'minimum-scale=1, initial-scale=1, width=device-width, shrink-to-fit=no, viewport-fit=cover',
applicationName: 'EveryGSM',
openGraph: {
title: 'EveryGSM',
description: '교내 프로젝트를 하나로 연결해주는 서비스.',
url: 'https://every.hellogsm.kr/',
siteName: 'EveryGSM',
images: [
{
url: '/images/openGraph.jpg',
},
],
locale: 'ko',
type: 'website',
},
icons: {
icon: '/images/Favicon.png',
},
};
여기서 빌드하면 파일들이 생기는데 이는 깃허브 저장소에 들어갈 필요가 없다. git ignore에서 제외한다.
**/public/sw.js
**/public/workbox-*.js
**/public/worker-*.js
**/public/sw.js.map
**/public/workbox- *.js.map
**/public/worker-*.js.map
빌드 후 실행한다.
npm run build && npm run dev
그럼 아래 두 파일이 생성된다.
로컬에서 어플리케이션을 열어 lighthouse를 돌리면 pwa 탭을 볼 수 있다. 여기서 문제점들을 해결한다.
나의 경우, page router에 맞게 설정하다 실패해서 다시 app router에 맞게 설정하니 성공했다.
아래 보이는 것과 같이 PWA 다운로드에 성공했다.
마치며
내가 PWA로 만든 서비스는 아래 링크에서 볼 수 있다!
EveryGSM
교내 프로젝트를 하나로 연결해주는 서비스.
every.hellogsm.kr
이렇게 PWA를 만들어보았다. 정말 재미있었고 좋은 기술이라 생각한다. 다음번에는 스토어에 출시까지 해보겠다!