vite-plugin-pwa + Workbox📥 포그라운드 및 백그라운드 모두에서 푸시 알림을 수신하고
🔔 알림 팝업(Notification)을 정상적으로 사용자에게 표시한다.
Web Push 인증서 (VAPID 키) 발급

vite-plugin-pwa로 관리하는 src/sw.js 방식public/firebase-messaging-sw.js 방식| 항목 | src/sw.js (vite-plugin-pwa) | public/firebase-messaging-sw.js |
|---|---|---|
| 관리 주체 | Vite / Workbox | Firebase 자체 |
| 파일 위치 | src/ → 빌드시 dist/sw.js | public/ → 빌드시 그대로 복사 |
| 백그라운드 알림 처리 | onBackgroundMessage() 직접 추가 | Firebase가 자동 참조 |
| 확장성 | 🟢 Workbox 사용 가능 (캐시 등) | 🔴 순수 백그라운드 알림만 가능 |
| 추천 용도 | ✅ PWA + 알림 모두 커스텀할 때 | 🔧 알림만 간단하게 쓸 때 |
나는 vite-plugin-pwa 방식으로 채택했다.
npm i vite-plugin-pwa
vite.config.ts 설정import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import { VitePWA } from 'vite-plugin-pwa';
import path from 'path';
export default defineConfig({
plugins: [
react(),
VitePWA({
strategies: 'injectManifest', // sw.js 직접 커스터마이징
srcDir: 'src',
filename: 'sw.js',
includeAssets: ['pwa-192x192.png', 'pwa-512x512.png'],
manifest: {
name: 'My App',
short_name: 'MyApp',
start_url: '/',
display: 'standalone',
background_color: '#ffffff',
theme_color: '#ffffff',
icons: [
{
src: '/pwa-192x192.png',
sizes: '192x192',
type: 'image/png',
},
{
src: '/pwa-512x512.png',
sizes: '512x512',
type: 'image/png',
},
],
},
injectManifest: {
globPatterns: ['**/*.{js,css,html,svg,png}'],
},
devOptions: {
enabled: true,
type: 'module',
navigateFallback: 'index.html',
navigateFallbackDenylist: [/^\/firebase-messaging-sw\.js$/],
// firebase에서 기본적으로 firebase-messaging-sw.js 파일을 참고하려고 하기 때문에 이를 방지하려고 옵션을 추가. src/sw.js 사용해야함
},
}),
],
resolve: {
alias: {
'@': path.resolve(__dirname, './src'),
},
},
});
src/sw.js 생성 (서비스워커 + 백그라운드 알림 포함)/// <reference lib="webworker" />
import {
cleanupOutdatedCaches,
createHandlerBoundToURL,
precacheAndRoute,
} from 'workbox-precaching';
import { clientsClaim } from 'workbox-core';
import { NavigationRoute, registerRoute } from 'workbox-routing';
precacheAndRoute(self.__WB_MANIFEST);
cleanupOutdatedCaches();
registerRoute(new NavigationRoute(createHandlerBoundToURL('index.html')));
self.skipWaiting();
clientsClaim();
// ✅ FCM 스크립트
importScripts('https://www.gstatic.com/firebasejs/9.6.10/firebase-app-compat.js');
importScripts('https://www.gstatic.com/firebasejs/9.6.10/firebase-messaging-compat.js');
firebase.initializeApp({
apiKey: 'YOUR_API_KEY',
authDomain: '...',
projectId: '...',
storageBucket: '...',
messagingSenderId: '...',
appId: '...',
});
const messaging = firebase.messaging();
messaging.onBackgroundMessage((payload) => {
console.log('📦 백그라운드 메시지:', payload);
const { title, body } = payload.notification || {};
self.registration.showNotification(title || '알림', {
body: body || '내용 없음',
icon: '/pwa-192x192.png',
});
});
src/firebase.js)import { initializeApp } from 'firebase/app';
import { getMessaging, getToken, onMessage } from 'firebase/messaging';
const app = initializeApp({
apiKey: 'YOUR_API_KEY',
authDomain: '...',
projectId: '...',
messagingSenderId: '...',
appId: '...',
});
export const requestPermission = async () => {
const permission = await Notification.requestPermission();
if (permission === 'granted') {
const registration = await navigator.serviceWorker.ready;
const token = await getToken(getMessaging(app), {
vapidKey: 'YOUR_VAPID_KEY',
serviceWorkerRegistration: registration,
});
// 중요 여기 토큰 값이 메세지 테스트에 필요함
console.log('🔥 FCM 토큰:', token);
return token;
}
};
export const listenForegroundMessage = () => {
const messaging = getMessaging(app);
onMessage(messaging, (payload) => {
console.log('📥 포그라운드 메시지:', payload);
const { title, body } = payload.notification || {};
if (Notification.permission === 'granted') {
new Notification(title ?? '알림', {
body: body ?? '내용 없음',
icon: '/pwa-192x192.png',
});
}
});
};
useEffect(() => {
requestPermission();
listenForegroundMessage();
}, []);
npm run buildnpm run preview아까 console에 찍었던 token 값을 저기 FCM 등록 토큰 추가 에 넣으면 된다

| 문제 | 해결 방법 |
|---|---|
| 알림 안 뜸 | 윈도우/브라우저 알림 설정 켜기 |
| icon 경로 오류 | /pwa-192x192.png 로 절대경로 사용 |
| 서비스워커 등록 안 됨 | vite.config.ts + src/sw.js 위치 확인 |
| DevTools 열려 있음 | 닫고 다시 테스트 (특히 백그라운드) |