블로그 목록

PWA에 Push Notification 보내기 — FCM으로 서버 없이 구현하기

2025-04-157 min read
PWAPush NotificationFCMFirebaseService Worker

이전 글: Vite로 PWA 만들기 — Service Worker 등록과 manifest, 업데이트 전략

PWA에서 푸시 알림을 Service Worker로 구현할 수는 있지만, 서버 없이 완전히 구현하는 건 어렵거나 제한적이다. 이 글은 그 이유를 정리하고, 서버를 직접 운영하지 않고 FCM(Firebase Cloud Messaging) 으로 같은 효과를 얻는 과정을 정리한다.

Push Notification에 필요한 3가지 요소

  1. Service Worker 백그라운드에서 푸시 이벤트를 수신하고 알림을 띄우는 데 사용. → 클라이언트 측.
  2. Push API + Notification API 브라우저에서 푸시 알림 수신 및 표시를 위한 API.
  3. 푸시 메시지를 보낼 서버 사용자의 브라우저(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에 푸시 보내기 — 전체 흐름

  1. Firebase 프로젝트 생성
  2. Firebase Messaging 설정 (웹 앱 등록 & VAPID 키 발급)
  3. PWA에 Firebase SDK & Service Worker 등록
  4. 브라우저에서 FCM 토큰 발급 및 알림 수신 설정
  5. 푸시 메시지 전송 (콘솔 / 서버 / 테스트 툴)

1. Firebase 프로젝트 생성

  1. Firebase 콘솔 접속
  2. 새 프로젝트 생성
  3. "웹 앱 추가" 클릭해서 앱 등록
  4. 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/브라우저 단에서 알림이 막혀 있으면 아무것도 보이지 않는다. 디버깅하다가 시간을 가장 많이 쓴 부분이기도 했다.

  1. Chrome 알림 허용 — 사이트 권한에서 알림을 허용
  2. macOS의 경우, 시스템 설정에서 Chrome 알림 허용 — Chrome 자체에 알림 권한이 없으면 사이트 권한과 무관하게 표시되지 않는다.

정리

  • PWA 푸시 알림은 수신은 클라이언트만으로 가능하지만 발신은 인증된 서버가 필요하다.
  • 운영 서버를 직접 만들기 부담스럽다면 FCM으로 그 역할을 대신할 수 있다.
  • 구성은 ① Firebase 프로젝트 ② VAPID 키 ③ 클라이언트 토큰 발급 ④ firebase-messaging-sw.js 등록 ⑤ 콘솔에서 토큰으로 발송, 다섯 단계로 끝난다.
  • OS/브라우저 알림 권한을 잊지 말 것.

PWA 기본 구성과 서비스 워커 두 파일(serviceWorkerRegistration.ts, service-worker.ts)의 역할이 궁금하다면 Vite로 PWA 만들기 글에서 정리해 두었다.