You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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部署配置

  1. 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;
  1. HTTPS要求:Vercel默认提供HTTPS环境,满足Service Worker运行条件;本地开发需使用next dev --https启动项目。

  2. Service Worker更新监听:可选添加前端监听,提示用户刷新页面获取更新:

useEffect(() => {
  if ('serviceWorker' in navigator) {
    navigator.serviceWorker.addEventListener('controllerchange', () => {
      window.location.reload();
    });
  }
}, []);

关键注意事项

  • Service Worker可能被浏览器空闲回收,因此每次激活时都会重新读取IndexedDB数据并设置定时器,确保提醒不丢失。
  • IndexedDB用于持久化存储任务数据,避免Service Worker重启后数据丢失。
  • sessionToken Cookie会通过credentials: 'include'自动携带到API请求中,无需额外处理。

内容的提问来源于stack exchange,提问作者Manu G

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.17 15:25:19