모바일 PWA 수신안됨 트러블슈팅

2025. 7. 8. 11:11·🌴트러블슈팅

🚧 발생한 문제 정리

1. 알림 권한 요청 누락

현상
처음 앱을 설치한 사용자는 알림 권한을 수락하지 않았기 때문에, 알림 자체가 전송되지 않음.

원인
알림 권한 요청 로직이 없거나 조건문이 너무 제한적으로 설정되어 있었음.

해결 방법

  • 사용자가 앱에 진입했을 때 자동으로 Notification.requestPermission() 호출
  • iOS PWA의 경우, 유저가 명시적으로 클릭할 수 있도록 알림 권한 배너 표시
 
if (Notification.permission === "default") {
  const permission = await Notification.requestPermission();
  if (permission === "granted") {
    await requestPermissionAndToken(); // FCM 토큰 등록
  }
}

✅ 알림 권한을 먼저 요청하지 않으면 아무리 푸시를 보내도 표시되지 않음.

 

2. FCM 토큰이 한 계정당 하나만 유지됨

현상
모바일에서 알림을 받아야 하는데, 열려있지 않았던 노트북의 localhost 주소로 알림이 도착함.

원인
백엔드에서 하나의 계정당 하나의 FCM 토큰만 저장하는 구조였기 때문에, 가장 마지막에 등록된 기기의 토큰만 유지됨.

해결 방법

  • 프론트엔드에서 모바일(PWA) 환경에서만 FCM 토큰을 등록하도록 제한
  • 각 기기에서 진입 시 항상 FCM 토큰을 재등록하여 사용자가 원하는 기기에서 알림을 받게 유도
const isMobile = /Android|iPhone|iPad|iPod/i.test(navigator.userAgent);
if (isMobile) {
  requestPermissionAndToken(); // 항상 재등록
}

✅ 한 계정이 여러 기기를 사용하더라도, 가장 마지막에 등록한 토큰으로 알림이 전송됨.

 

3. 백그라운드 상태에서 알림이 오지 않음

현상
앱이 포그라운드일 때는 알림이 잘 오지만, 앱이 꺼져 있거나 백그라운드 상태일 때는 알림이 표시되지 않음.

원인

  • foreground에서는 new Notification() 또는 showNotification()으로 처리되지만
  • 백그라운드는 Service Worker에서 push 이벤트를 수신하고 self.registration.showNotification()으로 수동 처리해야 함

해결 방법

  • firebase-messaging-sw.js 또는 커스텀 service worker에 아래 코드를 추가:
self.addEventListener("push", function (event) {
  const data = event.data.json();
  self.registration.showNotification(data.notification.title, {
    body: data.notification.body,
    icon: "/images/favicon/96x96.png",
    tag: "bg-noti",
  });
});

✅ 백그라운드 알림은 service worker 레벨에서 직접 표시해줘야 한다.

 

4. iOS PWA에서는 알림 권한 요청 UI가 아예 보이지 않음

현상
iOS Safari에서 PWA로 홈화면에 추가한 앱은 알림 권한 요청이 Notification.requestPermission() 호출만으로는 동작하지 않음. 유저가 권한을 직접 부여할 UI가 보이지 않음.

해결 방법

  • 앱이 iOS + PWA + 권한 없음 상태일 때, 커스텀 배너를 띄워 수동으로 알림 권한을 요청하도록 유도
if (isIOS && isStandalone && Notification.permission === "default") {
  setShowPermissionBanner(true); // 알림 허용 배너 표시
}

✅ iOS는 사용자와의 명시적 상호작용을 유도해야만 권한 요청이 작동함

 

 

5. 알림이 두 번 수신되는 문제: tag 옵션의 필요성

현상

포그라운드 상태에서 알림을 받을 때, 동일한 이벤트에 대한 알림이 두 번씩 수신되는 문제가 발생했습니다. 예를 들어 이벤트 취소 알림이 다음과 같이 중복 수신됨:

📩 이벤트가 취소되었어요
📩 이벤트가 취소되었어요

원인

  • onMessage()를 통해 포그라운드에서 수신한 알림을 registration.showNotification()으로 표시
  • 동시에 백그라운드용 Service Worker에서도 동일한 FCM payload를 받아 같은 알림을 showNotification()으로 또 표시함
  • 즉, 하나의 알림이 브라우저와 Service Worker 양쪽에서 표시됨

해결 방법

알림을 덮어씌우기 위해 NotificationOptions의 tag 속성을 활용했습니다.

//custom-sw.js

messaging.onBackgroundMessage((payload) => {
  console.log("📩 Background message received:", payload);
  console.log("📬 도착한 Background 알림 내용:", { title, body });

  self.registration.showNotification(title, {
    body,
    icon: "/images/favicon/96x96.png",
    tag: `event-${eventId}`,
    // 같은 태그면 알림이 덮어씌워지므로 유사 알림 중복도 막을 수 있음
    data: {
      eventId: payload.data?.eventId,
    },
    data: {
      url: `/detail/${payload.data?.eventId}`,
    },
  });
});
//use-fcm-handler.tsx
...
 // foreground 알림
      if (Notification.permission === "granted") {
        navigator.serviceWorker.ready.then((registration) => {
          registration.showNotification(title, {
            body,
            icon: "/images/favicon/96x96.png",
            tag: "foreground-noti",
            renotify: true,
          } as NotificationOptions);
        });
      }

 

추가 설명: tag란?

  • 동일한 tag를 가진 알림은 브라우저에서 하나만 유지
  • 새로 알림이 오면 이전 알림을 덮어씌움
  • 알림이 여러 번 표시되지 않게 하고 싶을 때 매우 유용
  • renotify: true를 설정하면 덮어씌우면서도 진동/소리 재생 가능

 

 

 


 

  • FCM은 단순히 메시지를 보내는 것 이상으로, **각 플랫폼과 상황(iOS, Android, Desktop, PWA, 포그라운드/백그라운드)**에 맞게 동작을 분기해야 함
  • 모바일 환경은 항상 사용자와의 상호작용 기반으로 권한 요청을 유도해야 하며, 특히 iOS는 더 많은 예외 처리가 필요
  • 백엔드 구조가 기기별 토큰을 구분하지 못할 경우, 프론트엔드가 전략적으로 어떤 기기에서 토큰을 등록하게 할지 컨트롤할 필요가 있음

특히 서비스워커의 중요성과 기기별 토큰 관리 전략의 필요성을 절실히 느꼈다..

실사용자에게 정확하게 알림을 전달하는 기능을 위해 많은 테스트와 디버깅을 거쳐 구조적으로 안정화시킬 수 있었다^^

 

'🌴트러블슈팅' 카테고리의 다른 글

AWS 이미지 url 가져올때 이미지 깨짐 해결  (0) 2024.12.27
git 충돌 - 트러블 슈팅  (1) 2024.11.12
'🌴트러블슈팅' 카테고리의 다른 글
  • AWS 이미지 url 가져올때 이미지 깨짐 해결
  • git 충돌 - 트러블 슈팅
gprorogpfus
gprorogpfus
:- >
  • gprorogpfus
    gpfusdldks
    gprorogpfus
  • 전체
    오늘
    어제
    • 분류 전체보기 (56)
      • 🎭JavaScript (2)
      • 👚 CSS (1)
      • ⚛️ React (13)
      • 🌃 Next.js (6)
        • 🔜 next.js-study (3)
      • 🥏TypeScript (10)
      • 🏴알고리즘 (2)
      • 🌴트러블슈팅 (3)
      • ⛲ 프로젝트 (6)
        • 👖gproro-shop-app (8)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
    • 글쓰기
  • 링크

  • 공지사항

  • 인기 글

  • 태그

    react
    TypeScript
    GIT
    Redux
    JavaScript
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.2
gprorogpfus
모바일 PWA 수신안됨 트러블슈팅
상단으로

티스토리툴바