PWA 설정 추가
PWA(Progressive Web App)를 설정하는 과정은 웹 앱에 앱처럼 설치 가능한 기능과 오프라인 동작 기능을 추가하는 일입니다.
https://github.com/inetsos/downtown
GitHub - inetsos/downtown: 동네 포털 - Vue 3 + Firebase
동네 포털 - Vue 3 + Firebase. Contribute to inetsos/downtown development by creating an account on GitHub.
github.com
✅ PWA 설정 방법 (전체 흐름)
- manifest.webmanifest 작성
- service-worker.js 구현
- HTML에 manifest와 meta 태그 추가
- 웹 서버 HTTPS 구성 (또는 localhost)
- (선택) vite-plugin-pwa 등 도구 사용
- 브라우저에서 서비스 워커 등록
🔷 1. manifest.json 설정
웹 앱의 설치 정보를 담는 JSON 파일로, 앱 이름, 아이콘, 시작 URL 등을 지정합니다.
📄 public/manifest.webmanifest
{
"name": "우리 동네",
"short_name": "우리동네",
"start_url": "/",
"display": "standalone",
"background_color": "#ffffff",
"theme_color": "#1976D2",
"description": "우리 동네 지역 기반 상점 정보 서비스",
"icons": [
{
"src": "/pwa-192x192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "/pwa-512x512.png",
"sizes": "512x512",
"type": "image/png"
}
]
}
매니페스트는 "우리 동네" 앱을 모바일이나 데스크탑에 설치했을 때, 앱처럼 보이도록 만드는 설정이에요.
📄 필드 설명
속성명 | 설명 |
"name" | 앱의 전체 이름 (우리 동네) 홈 화면에 설치될 때 사용자에게 보여져요. |
"short_name" | 짧은 이름 (우리동네) 공간이 좁은 곳(홈 화면 아이콘 아래 등)에서 사용돼요. |
"start_url" | 앱 실행 시 시작될 경로. /는 루트 페이지를 의미해요. |
"display" | "standalone"은 앱을 브라우저 없이 네이티브 앱처럼 보이게 해줘요 (주소창 등 없음). |
"background_color" | 앱이 로딩될 때의 배경 색상. 로딩 화면 등에서 사용돼요. |
"theme_color" | 브라우저 UI의 테마 색상. 안드로이드에서는 상태바 색상으로 반영되기도 해요. |
"description" | 앱에 대한 간단한 설명 – 사용자가 설치 전에 볼 수도 있어요. |
"icons" | 앱 설치 시 사용될 아이콘 이미지 목록. 각기 해상도에 맞는 이미지들이 지정돼 있어요. |
🔷 2. service-worker.js 설정
브라우저와 서버 사이에서 동작하는 백그라운드 스크립트입니다. 오프라인 지원, 캐싱, 푸시 알림 등을 담당합니다.
📄 public/service-worker.js
const CACHE_NAME = 'pwa-cache-v1';
const OFFLINE_URL = '/offline.html';
// 설치 시 캐시할 리소스
self.addEventListener('install', event => {
event.waitUntil(
caches.open(CACHE_NAME).then(cache =>
cache.addAll([
'/',
'/index.html',
'/offline.html',
'/pwa-192x192.png',
'/pwa-512x512.png'
])
)
);
self.skipWaiting();
});
// 활성화 시 오래된 캐시 정리
self.addEventListener('activate', event => {
event.waitUntil(
caches.keys().then(keys =>
Promise.all(
keys.map(key => {
if (key !== CACHE_NAME) {
return caches.delete(key);
}
})
)
)
);
self.clients.claim();
});
// fetch 요청 처리
self.addEventListener('fetch', event => {
event.respondWith(
fetch(event.request).catch(() => caches.match(event.request).then(response =>
response || caches.match(OFFLINE_URL)
))
);
});
🔹 CACHE_NAME과 OFFLINE_URL
- CACHE_NAME: 현재 캐시의 이름. 나중에 캐시 정리 시 이 이름을 기준으로 오래된 캐시를 제거해요.
- OFFLINE_URL: 오프라인 상태일 때 보여줄 HTML 파일 경로.
🛠️ install 이벤트
self.addEventListener('install', event => { ... })
- 서비스 워커가 처음 등록될 때 실행됩니다.
- caches.open()으로 캐시를 열고 cache.addAll()로 리소스를 미리 저장해둬요.
- 즉, 앱 로딩에 필요한 핵심 파일들을 오프라인에서도 쓸 수 있도록 미리 캐시하는 거예요.
- self.skipWaiting()은 기존 서비스 워커가 있어도 기다리지 않고 즉시 새 워커로 교체되게 합니다.
♻️ activate 이벤트
self.addEventListener('activate', event => { ... })
- 새 서비스 워커가 활성화될 때 실행됩니다.
- caches.keys()로 저장된 모든 캐시 목록을 가져와서, 현재 CACHE_NAME과 다른 이전 캐시는 삭제해요.
- 이렇게 하면 오래된 캐시가 쌓이는 걸 방지할 수 있어요.
- self.clients.claim()은 해당 워커가 모든 클라이언트 탭에 즉시 적용되도록 합니다.
🌐 fetch 이벤트
self.addEventListener('fetch', event => { ... })
- 사용자가 웹 페이지나 리소스를 요청할 때마다 실행돼요.
- 우선 fetch()로 네트워크에서 리소스를 가져오려고 시도하고,
- **만약 실패(예: 오프라인)**하면 → caches.match()로 캐시에서 찾고, → 그래도 없으면 OFFLINE_URL을 보여줘요.
🔷 3. HTML에 PWA 태그 삽입
📄 index.html (또는 Vue/React 앱의 public/index.html)
<!doctype html>
<html lang="ko">
<head>
<meta charset="UTF-8" />
<link rel="manifest" href="/manifest.webmanifest" />
<meta name="theme-color" content="#1976D2">
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<!-- 카카오 주소 검색 위젯 -->
<script src="https://t1.daumcdn.net/mapjsapi/bundle/postcode/prod/postcode.v2.js"></script>
<!-- 카카오 로그인 SDK-->
<script src="https://developers.kakao.com/sdk/js/kakao.js"></script>
<!-- 네이버 지도 API -->
<script src="https://oapi.map.naver.com/openapi/v3/maps.js?ncpKeyId=s781heidm9&submodules=geocoder"></script>
<title>우리 동네 - Vue + Firebase 사이드 프로젝트</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.js"></script>
</body>
</html>
🔷 4. HTTPS에서 서비스 워커 등록 (JS)
웹 앱이 로드될 때 브라우저에서 서비스 워커를 등록해야 합니다.
📄 main.js 또는 index.js
// src/main.js
import { createApp } from 'vue'
import App from './App.vue'
import 'vuetify/styles'
import vuetify from './plugins/vuetify'
import { createPinia } from 'pinia'
import router from './router'
import '@mdi/font/css/materialdesignicons.css'
import { useAuthStore } from './stores/authStore'
const app = createApp(App)
app.use(vuetify)
app.use(createPinia())
app.use(router)
const authStore = useAuthStore()
authStore.initAuth() // 로그인 상태 복원
app.mount('#app')
// 서비스 워커 등록
if ('serviceWorker' in navigator) {
window.addEventListener('load', () => {
navigator.serviceWorker.register('/service-worker.js')
.then(registration => {
console.log('Service Worker registered with scope:', registration.scope);
})
.catch(error => {
console.error('Service Worker registration failed:', error);
});
});
}
🔷 5. (선택) vite-plugin-pwa 또는 workbox 등 사용
Vite를 사용 중이라면 vite-plugin-pwa를 사용하는 것이 편리합니다.
npm install vite-plugin-pwa
📄 vite.config.js
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import vuetify from 'vite-plugin-vuetify'
import path from 'path'
import { VitePWA } from 'vite-plugin-pwa'
export default defineConfig({
plugins: [
vue(),
vuetify({ autoImport: true }),
VitePWA({ // ✅ PWA 설정 추가
registerType: 'autoUpdate',
includeAssets: [
'favicon.ico',
'robots.txt',
'apple-touch-icon.png'
],
manifest: {
name: 'Downtown Life',
short_name: 'Downtown',
description: '우리 동네',
theme_color: '#1867C0', // Vuetify 기본 테마 색상
background_color: '#ffffff',
display: 'standalone',
start_url: '/',
icons: [
{
src: '/pwa-192x192.png',
sizes: '192x192',
type: 'image/png'
},
{
src: '/pwa-512x512.png',
sizes: '512x512',
type: 'image/png'
}
]
}
})
],
server: {
proxy: {
'/naver-api': {
target: 'https://openapi.naver.com/v1',
changeOrigin: true,
rewrite: path => path.replace(/^\/naver-api/, '')
}
}
},
resolve: {
alias: {
'@': path.resolve(__dirname, 'src'),
},
},
build: {
chunkSizeWarningLimit: 1500,
rollupOptions: {
output: {
manualChunks(id) {
if (id.includes('node_modules')) {
if (id.includes('vuetify')) return 'vuetify'
if (id.includes('firebase')) return 'firebase'
if (id.includes('vue-router')) return 'vue-router'
if (id.includes('vue')) return 'vue'
return 'vendor'
}
}
}
}
}
})
🔷 6. 테스트 및 설치 확인
- 브라우저에서 앱을 열고 DevTools > Application > Manifest 탭 확인
- Service Worker가 활성화되었는지 확인
- 네트워크 탭에서 오프라인으로 설정 후 동작 확인
- 주소창에 + 설치 아이콘이 뜨는지 확인
✅ 결과
PWA 설정이 완료되면 사용자는:
- 홈 화면에 설치할 수 있고
- 오프라인에서도 앱을 실행할 수 있으며
- 나중에는 푸시 알림이나 백그라운드 동기화 등 기능도 확장할 수 있습니다.
'예약 포털 (Vue3 + Firebase) - 서비스 오픈까지' 카테고리의 다른 글
40. Vue3 + Firebase 프로젝트 '우리 동네' - 웹앱에서 GPS 사용 (2) | 2025.06.30 |
---|---|
39. Vue3 + Firebase 프로젝트 '우리 동네' - 쿠폰 관리 시스템 (0) | 2025.06.28 |
37. 예약 포털 (Vue3 + Firebase) - 네이버 로그인 구현 (0) | 2025.06.19 |
36. 예약 포털 (Vue3 + Firebase) - 카카오 로그인 구현 (0) | 2025.06.19 |
35. 예약 포털 (Vue3 + Firebase) - 소셜 로그인 구현에 필요한 것들 (1) | 2025.06.19 |