Web Push 알림은 사용자가 브라우저를 닫아도 서버에서 메시지를 전달할 수 있는 강력한 기능입니다. React 앱에 Web Push를 추가하려면 서비스 워커, Notification API, Push API, 그리고 VAPID 키를 사용하는 백엔드가 필요합니다. 아래 단계를 그대로 따라 하면 기본 구현을 완성할 수 있습니다.
1. 동작 원리 이해
전체 흐름은 다음과 같습니다. 1) 서비스 워커 등록 2) 알림 권한 요청 3) PushManager로 구독 생성 4) 구독 정보를 백엔드로 저장 5) 백엔드가 web-push로 푸시 전송 6) 서비스 워커가 push 이벤트에서 알림 표시합니다.
2. 사전 준비 체크리스트
1) HTTPS 환경 또는 localhost 2) React 빌드가 sw.js를 서비스할 수 있는 public 디렉터리 3) Node.js 백엔드와 web-push 라이브러리 4) VAPID 공개/비공개 키 쌍이 필요합니다.
3. 서비스 워커 작성
public 폴더에 sw.js 파일을 생성합니다. push 이벤트에서 payload를 읽어 알림을 표시하고, 클릭 시 이동 동작을 처리합니다.
// public/sw.js
self.addEventListener('push', event => {
const data = event.data ? event.data.json() : {};
const title = data.title || '새 알림';
const options = {
body: data.body || '알림을 확인하세요.',
icon: data.icon || '/icons/icon-192.png',
badge: data.badge || '/icons/badge-72.png',
data: { url: data.url || '/' },
};
event.waitUntil(self.registration.showNotification(title, options));
});
self.addEventListener('notificationclick', event => {
event.notification.close();
const url = event.notification.data && event.notification.data.url ? event.notification.data.url : '/';
event.waitUntil(clients.matchAll({ type: 'window', includeUncontrolled: true }).then(clientList => {
for (const client of clientList) {
if (client.url === url && 'focus' in client) {
return client.focus();
}
}
if (clients.openWindow) {
return clients.openWindow(url);
}
}));
});4. React에서 서비스 워커 등록 및 구독
앱 시작 시 서비스 워커를 등록하고 권한을 요청한 뒤 Push 구독을 생성합니다. VAPID 공개키는 .env에 REACT_APP_VAPID_PUBLIC_KEY로 주입합니다.
// src/push.js
export const urlBase64ToUint8Array = (base64String) => {
const padding = '='.repeat((4 - (base64String.length % 4)) % 4);
const base64 = (base64String + padding).replace(/-/g, '+').replace(/_/g, '/');
const rawData = window.atob(base64);
const outputArray = new Uint8Array(rawData.length);
for (let i = 0; i < rawData.length; ++i) {
outputArray[i] = rawData.charCodeAt(i);
}
return outputArray;
};
export async function initPush() {
if (!('serviceWorker' in navigator) || !('PushManager' in window)) {
return { ok: false, reason: 'unsupported' };
}
const registration = await navigator.serviceWorker.register('/sw.js');
const permission = await Notification.requestPermission();
if (permission !== 'granted') {
return { ok: false, reason: 'permission-denied' };
}
const vapidPublicKey = process.env.REACT_APP_VAPID_PUBLIC_KEY;
if (!vapidPublicKey) throw new Error('Missing REACT_APP_VAPID_PUBLIC_KEY');
const subscription = await registration.pushManager.subscribe({
userVisibleOnly: true,
applicationServerKey: urlBase64ToUint8Array(vapidPublicKey),
});
await fetch('/api/subscribe', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(subscription),
});
return { ok: true };
}
// src/App.js
import { useEffect } from 'react';
import { initPush } from './push';
function App() {
useEffect(() => {
initPush().catch(console.error);
}, []);
return <div>React Web Push 예제입니다.</div>;
}
export default App;5. VAPID 키 생성
web-push 라이브러리로 서버에서 키를 생성하고 환경변수로 관리합니다.
// scripts/generate-keys.js
const webpush = require('web-push');
const keys = webpush.generateVAPIDKeys();
console.log(keys); // { publicKey, privateKey }출력된 publicKey는 클라이언트 .env의 REACT_APP_VAPID_PUBLIC_KEY에, privateKey는 서버 환경변수 VAPID_PRIVATE_KEY로 설정합니다.
6. Node 백엔드 구성
구독을 저장하고 알림을 발송하는 간단한 Express 서버입니다. 실제 운영에서는 DB에 구독을 저장하고 인증을 적용합니다.
// server/index.js
require('dotenv').config();
const express = require('express');
const webpush = require('web-push');
const app = express();
app.use(express.json());
const PUBLIC_VAPID_KEY = process.env.VAPID_PUBLIC_KEY;
const PRIVATE_VAPID_KEY = process.env.VAPID_PRIVATE_KEY;
webpush.setVapidDetails('mailto:admin@example.com', PUBLIC_VAPID_KEY, PRIVATE_VAPID_KEY);
const subscriptions = new Set();
app.post('/api/subscribe', (req, res) => {
subscriptions.add(req.body);
res.status(201).json({ ok: true });
});
app.post('/api/notify', async (req, res) => {
const payload = JSON.stringify({
title: req.body.title || '새 소식',
body: req.body.body || '알림을 확인하세요.',
icon: '/icons/icon-192.png',
badge: '/icons/badge-72.png',
url: req.body.url || '/',
});
const results = [];
for (const sub of [...subscriptions]) {
try {
await webpush.sendNotification(sub, payload);
results.push({ ok: true });
} catch (err) {
results.push({ ok: false, error: err.message });
if (err.statusCode === 410 || err.statusCode === 404) {
subscriptions.delete(sub);
}
}
}
res.json({ sent: results.length });
});
app.listen(4000, () => {
console.log('Push server on http://localhost:4000');
});7. 알림 트리거 테스트
서버가 실행 중이라면 클라이언트나 관리자 페이지에서 /api/notify로 요청을 보내 테스트합니다.
// 예: 테스트 트리거
fetch('/api/notify', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
title: '프로모션 알림',
body: '지금 접속하면 20% 할인!',
url: '/promo',
}),
});8. 권한 및 에러 처리 팁
1) 사용자 흐름 중 명확한 순간에 권한을 요청합니다. 2) Notification.requestPermission 결과가 denied면 재요청하지 않고 대체 UI를 제공합니다. 3) 구독이 410 Gone으로 만료되면 서버에서 삭제합니다. 4) iOS 사파리 등 브라우저별 제한을 고려하고 최신 버전을 테스트합니다.
9. 프로덕션 베스트 프랙티스
1) 구독과 사용자 계정을 매핑해 타겟팅 메시지를 발송합니다. 2) TTL과 주기를 조절해 스팸을 피합니다. 3) 아이콘과 배지를 브랜드 가이드에 맞게 구성합니다. 4) 서버 키는 환경변수로 관리하고 리포지토리에 커밋하지 않습니다. 5) PWA 설치 환경에서 알림 도달률이 높으므로 웹앱 설치를 유도합니다.
10. Firebase Cloud Messaging을 사용할 때
FCM을 사용하면 브라우저별 푸시 서비스 연동이 간소화됩니다. 다만 커스텀 서버 없이도 발송이 가능하지만 토큰 관리 및 Firebase 초기화가 필요합니다. 본 문서의 VAPID 방식은 자체 서버로 완전 제어가 가능하다는 장점이 있습니다.
위 구조로 구현하면 React 앱에서 표준 Web Push 알림을 안정적으로 제공할 수 있습니다. 먼저 개발 환경에서 작동을 확인하고, 이후 HTTPS 배포와 구독 저장소를 DB로 이전해 운영 품질을 높입니다.
'React' 카테고리의 다른 글
| React로 PDF 뷰어 및 다운로드 기능 구현하기 (0) | 2026.05.26 |
|---|---|
| React에서 상태 관리 없이 URL 파라미터로 데이터 전달하기 (0) | 2026.05.26 |
| React에서 이미지 드래그 앤 드롭 정렬 기능 구현하기 (0) | 2026.05.25 |
| React 컴포넌트에서 성능 저하 원인 분석 및 제거 방법 (0) | 2026.05.25 |
| React와 Firebase Firestore 실시간 데이터 연동하기 (0) | 2026.05.23 |