PWA에서 푸시 알림을 Service Worker로 구현할 수는 있지만, 서버 없이 완전히 구현하는 건 어렵거나 제한적이다. 이 글은 그 이유를 정리하고, 서버를 직접 운영하지 않고 FCM(Firebase Cloud Messaging) 으로 같은 효과를 얻는 과정을 정리한다.
Push Notification에 필요한 3가지 요소
- Service Worker 백그라운드에서 푸시 이벤트를 수신하고 알림을 띄우는 데 사용. → 클라이언트 측.
- Push API + Notification API 브라우저에서 푸시 알림 수신 및 표시를 위한 API.
- 푸시 메시지를 보낼 서버 사용자의 브라우저(Push Service)에 메시지를 전송하는 역할. → 이게 없으면 외부에서 브라우저로 푸시를 보낼 방법이 없다.
왜 서버 없이는 안 될까
- Push Notification은 브라우저 푸시 서비스(Google, Mozilla 등) 와 통신해야 하는데, 이 통신은 보통 VAPID 키로 인증된 서버에서만 가능하다.
- 클라이언트(PWA)만으로는 푸시 알림을 전송할 방법이 없다. 수신은 되지만, 발신은 서버가 필요하다.
서버 없이 비슷한 UX를 내는 방법
1) Local Notification — 사용자 상호작용 기반
사용자가 어떤 액션(버튼 클릭 등)을 했을 때 즉시 알림을 띄우는 방식. Notification API만 쓰면 되지만 백그라운드에서는 작동하지 않는다.
Notification.requestPermission().then((permission) => {
if (permission === "granted") {
new Notification("안녕하세요!", {
body: "이건 로컬 알림이에요.",
});
}
});
2) 타사 서비스 활용 — FCM
서버를 직접 운영하는 대신 Firebase Cloud Messaging을 사용한다. 클라이언트에서 토큰을 발급받고, Firebase 콘솔이나 간단한 클라우드 함수로 푸시를 보낼 수 있다. 이번에 채택한 방식이 이쪽이다.
결론
- 완전한 푸시 알림 구현(Push API)은 서버 없이는 불가능.
- 그러나 로컬 알림이나 FCM 같은 타사 서비스를 활용하면 운영 서버 없이도 비슷한 사용자 경험을 만들 수 있다.
FCM으로 PWA에 푸시 보내기 — 전체 흐름
- Firebase 프로젝트 생성
- Firebase Messaging 설정 (웹 앱 등록 & VAPID 키 발급)
- PWA에 Firebase SDK & Service Worker 등록
- 브라우저에서 FCM 토큰 발급 및 알림 수신 설정
- 푸시 메시지 전송 (콘솔 / 서버 / 테스트 툴)
1. Firebase 프로젝트 생성
- Firebase 콘솔 접속
- 새 프로젝트 생성
- "웹 앱 추가" 클릭해서 앱 등록
- SDK 설정 코드 복사
2. FCM용 Web Push 키 생성
- 프로젝트 설정 > 클라우드 메시징 탭
Web Push 인증 키 생성클릭 →VAPID 키생성- 공개 키는 PWA에서, 비공개 키는 서버에서 사용
3. PWA에 Firebase SDK & Service Worker 등록
디렉토리 구조
public/
└── firebase-messaging-sw.js
src/
└── main.js (또는 app.js)
설치
pnpm install firebase
main.js — PWA 초기화
import { initializeApp } from "firebase/app";
import { getMessaging, getToken, onMessage } from "firebase/messaging";
// Firebase 콘솔에서 가져온 설정값
const firebaseConfig = {
apiKey: "API_KEY",
authDomain: "PROJECT_ID.firebaseapp.com",
projectId: "PROJECT_ID",
messagingSenderId: "SENDER_ID",
appId: "APP_ID",
};
const app = initializeApp(firebaseConfig);
const messaging = getMessaging(app);
// Service Worker 등록
navigator.serviceWorker
.register("/firebase-messaging-sw.js")
.then((registration) => {
// FCM에 Service Worker 연결
messaging.useServiceWorker(registration);
// 권한 요청
Notification.requestPermission().then((permission) => {
if (permission === "granted") {
getToken(messaging, {
vapidKey: "YOUR_PUBLIC_VAPID_KEY",
}).then((token) => {
console.log("FCM Token:", token);
// 이 토큰을 서버에 저장
});
}
});
});
// 포그라운드 메시지 수신
onMessage(messaging, (payload) => {
console.log("Foreground 메시지 수신:", payload);
});
public/firebase-messaging-sw.js — 백그라운드용 서비스 워커
importScripts(
"https://www.gstatic.com/firebasejs/10.9.0/firebase-app-compat.js",
);
importScripts(
"https://www.gstatic.com/firebasejs/10.9.0/firebase-messaging-compat.js",
);
firebase.initializeApp({
apiKey: "API_KEY",
authDomain: "PROJECT_ID.firebaseapp.com",
projectId: "PROJECT_ID",
messagingSenderId: "SENDER_ID",
appId: "APP_ID",
});
const messaging = firebase.messaging();
messaging.onBackgroundMessage((payload) => {
console.log("[firebase-messaging-sw.js] 백그라운드 메시지 수신:", payload);
const notificationTitle = payload.notification.title;
const notificationOptions = {
body: payload.notification.body,
icon: "/icon.png",
};
self.registration.showNotification(notificationTitle, notificationOptions);
});
포그라운드 메시지는 main.js의 onMessage로, 백그라운드 메시지는 별도 서비스 워커의 onBackgroundMessage로 각각 받는다는 점이 중요하다.
4. 푸시 메시지 전송
전용 서버나 클라우드 함수를 만들기 전에, Firebase 콘솔에서 바로 테스트가 가능하다.
- 클라우드 메시징 > 새 알림 만들기 > 토큰으로 전송
getToken으로 받은 FCM 토큰을 입력해 발송
사전에 설정해야 할 것
코드를 다 맞춰도 OS/브라우저 단에서 알림이 막혀 있으면 아무것도 보이지 않는다. 디버깅하다가 시간을 가장 많이 쓴 부분이기도 했다.
- Chrome 알림 허용 — 사이트 권한에서 알림을 허용
- macOS의 경우, 시스템 설정에서 Chrome 알림 허용 — Chrome 자체에 알림 권한이 없으면 사이트 권한과 무관하게 표시되지 않는다.
정리
- PWA 푸시 알림은 수신은 클라이언트만으로 가능하지만 발신은 인증된 서버가 필요하다.
- 운영 서버를 직접 만들기 부담스럽다면 FCM으로 그 역할을 대신할 수 있다.
- 구성은 ① Firebase 프로젝트 ② VAPID 키 ③ 클라이언트 토큰 발급 ④
firebase-messaging-sw.js등록 ⑤ 콘솔에서 토큰으로 발송, 다섯 단계로 끝난다. - OS/브라우저 알림 권한을 잊지 말 것.
PWA 기본 구성과 서비스 워커 두 파일(serviceWorkerRegistration.ts, service-worker.ts)의 역할이 궁금하다면 Vite로 PWA 만들기 글에서 정리해 두었다.