如何在Next.js React应用中实现基于Service Worker的定时推送通知?
实现基于Service Worker的后台定时推送通知(无OneSignal)
核心思路
利用浏览器Service Worker的后台运行能力,结合IndexedDB存储任务数据,实现标签页关闭时的定时推送;通过Background Sync和前端主动消息通知机制,确保任务数据实时更新。
步骤1:前端申请通知权限并注册Service Worker
在「Reminders」功能页面的组件中,完成权限申请和Service Worker注册:
import { useEffect } from 'react'; const RemindersPage = () => { useEffect(() => { const initNotifications = async () => { if (!('Notification' in window) || !('serviceWorker' in navigator)) return; // 请求通知权限 const permission = await Notification.requestPermission(); if (permission !== 'granted') return; // 注册Service Worker await navigator.serviceWorker.register('/sw.js'); // 立即触发任务同步 const reg = await navigator.serviceWorker.ready; reg.sync.register('sync-tasks'); }; initNotifications(); }, []); // 页面其他逻辑... return <div>Reminders Content</div>; }; export default RemindersPage;
步骤2:编写Service Worker核心逻辑(public/sw.js)
创建public/sw.js文件,实现数据同步、定时提醒、通知推送功能:
// 初始化IndexedDB,存储任务数据 const openTaskDB = () => { return new Promise((resolve, reject) => { const request = indexedDB.open('TaskRemindersDB', 1); request.onupgradeneeded = (e) => { const db = e.target.result; if (!db.objectStoreNames.contains('tasks')) { db.createObjectStore('tasks', { keyPath: 'id' }); } }; request.onsuccess = () => resolve(request.result); request.onerror = () => reject(request.error); }); }; // 从API拉取任务并存储到IndexedDB const syncTasks = async () => { try { const res = await fetch('/api/property/tasks', { method: 'POST', credentials: 'include' // 自动携带sessionToken Cookie }); const tasks = await res.json(); const db = await openTaskDB(); const tx = db.transaction('tasks', 'readwrite'); const store = tx.objectStore('tasks'); await store.clear(); tasks.forEach(task => store.put(task)); await tx.complete(); // 同步完成后重新设置定时提醒 setupReminders(); } catch (err) { console.error('任务同步失败:', err); } }; // 设置定时提醒,检测任务到期时间 const setupReminders = async () => { const db = await openTaskDB(); const tx = db.transaction('tasks', 'readonly'); const tasks = await tx.objectStore('tasks').getAll(); await tx.complete(); tasks.forEach(task => { const now = new Date(); const nextDue = new Date(task.nextDue); const delay = nextDue.getTime() - now.getTime(); if (delay > 0) { setTimeout(() => { // 推送通知 showTaskNotification(task); // 通知后端更新lastDone fetch('/api/property/tasks/update-lastdone', { method: 'POST', credentials: 'include', body: JSON.stringify({ taskId: task.id }) }); // 重新同步任务,计算新的到期时间 syncTasks(); }, delay); } }); }; // 显示推送通知 const showTaskNotification = (task) => { self.registration.showNotification('任务提醒', { body: task.note || task.name, icon: '/icons/reminder-icon.png', // 替换为你的通知图标路径 data: { taskId: task.id } }); }; // 监听Background Sync事件,用于后台同步任务 self.addEventListener('sync', (event) => { if (event.tag === 'sync-tasks') { event.waitUntil(syncTasks()); } }); // 监听前端发来的消息,任务更新/删除时触发同步 self.addEventListener('message', (event) => { if (event.data.type === 'REFRESH_TASKS') { syncTasks(); } }); // Service Worker激活时立即同步任务 self.addEventListener('activate', (event) => { event.waitUntil(syncTasks()); }); // 处理通知点击事件,打开对应任务页面 self.addEventListener('notificationclick', (event) => { event.notification.close(); event.waitUntil( clients.openWindow(`/reminders/${event.notification.data.taskId}`) ); });
步骤3:前端触发任务数据更新
当任务被新增、修改或删除后,主动通知Service Worker同步最新数据:
// 示例:任务保存完成后触发同步 const handleSaveTask = async (taskData) => { // 调用API保存任务 await fetch('/api/property/tasks/save', { method: 'POST', credentials: 'include', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(taskData) }); // 通知Service Worker刷新任务 if ('serviceWorker' in navigator) { const reg = await navigator.serviceWorker.ready; reg.active.postMessage({ type: 'REFRESH_TASKS' }); } };
步骤4:Next.js与Vercel部署配置
- Service Worker缓存策略:在
next.config.js中配置禁止缓存sw.js,确保用户能获取最新版本:
/** @type {import('next').NextConfig} */ const nextConfig = { async headers() { return [ { source: '/sw.js', headers: [ { key: 'Cache-Control', value: 'no-cache, no-store, must-revalidate' }, ], }, ]; }, }; module.exports = nextConfig;
HTTPS要求:Vercel默认提供HTTPS环境,满足Service Worker运行条件;本地开发需使用
next dev --https启动项目。Service Worker更新监听:可选添加前端监听,提示用户刷新页面获取更新:
useEffect(() => { if ('serviceWorker' in navigator) { navigator.serviceWorker.addEventListener('controllerchange', () => { window.location.reload(); }); } }, []);
关键注意事项
- Service Worker可能被浏览器空闲回收,因此每次激活时都会重新读取IndexedDB数据并设置定时器,确保提醒不丢失。
- IndexedDB用于持久化存储任务数据,避免Service Worker重启后数据丢失。
sessionTokenCookie会通过credentials: 'include'自动携带到API请求中,无需额外处理。
内容的提问来源于stack exchange,提问作者Manu G
相关产品推荐
相关产品推荐

