본문 바로가기
AWS

PWA로 홈 화면에 앱처럼 설치하기

by 고홍 근성 2026. 8. 30.
반응형

HTTPS까지 붙이고 나니 욕심이 하나 더 생겼다. 휴대폰 홈 화면에 아이콘 하나 딱 박아두고, 눌렀을 때 브라우저 주소창 없이 앱처럼 뜨게 만들고 싶었다. PWA(Progressive Web App)라는 게 정확히 이 용도였다.


오프라인 캐싱은 일부러 안 넣었다

PWA라고 하면 흔히 인터넷이 끊겨도 동작하는 앱을 떠올리는데, 여기서는 이 기능을 의도적으로 뺐다. 이유를 설명하려면 먼저 서비스 워커(Service Worker)가 뭔지부터 짚어야 한다. 서비스 워커는 브라우저가 웹페이지와는 별도로 백그라운드에서 돌리는 스크립트다. 페이지를 닫아도 계속 살아있을 수 있고, 서버로 나가는 네트워크 요청을 중간에서 가로챌 수 있다는 게 핵심이다.

오프라인 캐싱은 이 서비스 워커를 이용해서, 서버가 보내준 응답(API 응답, 이미지, HTML 등)을 브라우저에 저장해뒀다가, 나중에 인터넷이 끊긴 상태에서 같은 요청이 오면 서버 대신 저장해둔 옛날 응답을 그대로 돌려주는 방식이다.

문제는 가계부 데이터는 항상 최신 서버 응답이어야 의미가 있다는 점이다. 오프라인 캐싱을 켜두면, 인터넷이 끊긴 상태에서 앱을 열었을 때 서비스 워커가 몇 시간 전에 캐싱해둔 옛날 잔액/지출 응답을 그대로 보여줄 수 있다. 사용자는 그게 최신 데이터인 줄 알고 착각하게 된다. 예를 들어 방금 결제해서 잔액이 줄었는데, 캐싱된 화면엔 옛날 잔액이 그대로 떠 있는 식이다. 이런 오해가 생기지 않도록, 이번에 다루는 범위는 딱 홈 화면에 앱처럼 설치되게 만드는 것까지로 정했다.


vite-plugin-pwa를 쓴 이유

매니페스트(앱 이름/아이콘/실행 방식을 브라우저에 알려주는 설정 파일)랑 서비스 워커를 손으로 직접 짤 수도 있는데, 아이콘 크기별 생성이나 캐시 무효화 전략처럼 실수하기 쉬운 부분이 많다. vite-plugin-pwa는 Vite 생태계에서 가장 널리 쓰이는 PWA 플러그인이라 그냥 이걸 골랐다.

npm install -D vite-plugin-pwa

frontend/vite.config.js에 이렇게 설정했다.

import { VitePWA } from 'vite-plugin-pwa'

export default defineConfig({
  plugins: [
    react(),
    VitePWA({
      registerType: 'autoUpdate',        // 새 버전 나오면 조용히 백그라운드 교체
      includeAssets: ['favicon.ico', 'apple-touch-icon-180x180.png'],
      manifest: {
        name: '부자가되',
        short_name: '부자가되',
        theme_color: '#B8892B',          // 상태바 색 (CSS --gold 변수와 통일)
        background_color: '#EEF3E6',     // 스플래시 배경 (CSS --paper 변수와 통일)
        display: 'standalone',           // 브라우저 UI 없이 독립된 앱처럼
        start_url: '/',
        icons: [
          { src: 'pwa-64x64.png', sizes: '64x64', type: 'image/png' },
          { src: 'pwa-192x192.png', sizes: '192x192', type: 'image/png' },
          { src: 'pwa-512x512.png', sizes: '512x512', type: 'image/png' },
          { src: 'maskable-icon-512x512.png', sizes: '512x512', type: 'image/png', purpose: 'maskable' },
        ],
      },
      workbox: {
        navigateFallbackDenylist: [/^\/api\//, /^\/files\//],   // 아래에서 설명
      },
    }),
  ],
})

registerType: 'autoUpdate'는 사용자한테 새 버전이 나왔는지 새로고침할지 안 물어보고 그냥 알아서 최신 서비스 워커로 갈아치우는 옵션이다. 개인 프로젝트 규모에서는 이게 더 단순하다. navigateFallbackDenylist가 좀 중요한데, 서비스 워커는 기본적으로 페이지 요청이 오프라인이면 캐시된 index.html로 대신 응답해도 된다고 판단한다(SPA 라우팅 지원용). 근데 /api//files/로 가는 요청까지 이렇게 처리되면, 진짜 서버 응답 대신 엉뚱한 HTML 페이지가 돌아가버린다. 그래서 이 두 경로만 이 규칙에서 빼뒀다.


아이콘은 자동 생성

매니페스트가 요구하는 아이콘이 크기별로 여러 개(64/192/512, 마스커블 버전, 파비콘, 애플 터치 아이콘)라 손으로 하나씩 만들기는 귀찮다. vite-plugin-pwa의 공식 동반 도구로 원본 로고 하나에서 전부 자동 생성했다.

npx pwa-assets-generator --preset minimal public/logo.png

public/logo.png(1024×1024 원본)에서 필요한 파일들이 frontend/public/에 한 번에 생성된다. 이 파일들은 매번 다시 만드는 게 아니라 한 번 생성해서 git에 커밋해뒀다 - 로고 자체를 안 바꾸는 한 다시 실행할 필요가 없다.


Nginx에 MIME 타입 설정 추가

vite-plugin-pwamanifest.webmanifest, sw.js 같은 파일을 자동 생성해주지만, Nginx는 .webmanifest 확장자의 MIME 타입을 기본으로 모른다. 이 설정은 코드가 아니라 서버의 nginx.conf 파일에 직접 추가해줘야 적용된다.

# PWA 매니페스트 - Nginx가 .webmanifest 확장자의 기본 MIME 타입을 몰라서 그냥 두면 application/octet-stream으로 나감
# PWA 스펙상 application/manifest+json이어야 브라우저가 설치 가능으로 정확히 인식함
location = /manifest.webmanifest {
    default_type application/manifest+json;
}

location = /경로는 정확히 이 경로 하나에만 매칭되는 문법이다. 이 location이 파일을 서빙하려면 root 지시어가 있어야 해서, server 블록 레벨로 옮겨 모든 location이 공유하도록 정리했다.


실제로 확인

curl -I https://berich.co.kr/sw.js   # 서비스 워커 파일 자체가 서빙되는지

크롬 개발자도구 → Application 탭 → Manifest에서 파싱된 내용이랑 Installability 체크 결과를 볼 수 있다. 안드로이드 크롬에서는 메뉴에 홈 화면에 추가 항목이 뜨고, 실제로 추가해서 열어보면 주소창 없이 앱처럼 전체화면으로 뜬다.


여기까지가 EC2 배포, S3 전환, 도메인/HTTPS, CI/CD, PWA까지 - 처음 EC2 인스턴스 하나 띄우던 날부터 이어진 인프라 작업의 마지막 편이다.

반응형

'AWS' 카테고리의 다른 글

GitHub Actions로 배포 자동화하기  (0) 2026.08.30
도메인 연결하고 HTTPS 적용하기  (0) 2026.08.30
파일 저장소를 S3로 분리하기  (0) 2026.08.30
Docker로 EC2에 배포하기  (0) 2026.08.30