如何为Nuxt 3搭配vite-pwa添加自定义Service Worker?
针对你的困惑逐一解答:
1. 自定义Service Worker实现推送的思路是否正确?
完全正确。无需依赖OneSignal这类第三方服务,通过编写自定义Service Worker,结合浏览器的Notification API(展示通知)、Push API(服务器触发推送)或Periodic Background Sync API(定时本地触发推送),就能实现用户离线/未打开应用时的通知推送。
注意:浏览器强制要求通知必须是用户可见的,所以配置推送订阅时userVisibleOnly必须设为true;另外Periodic Background Sync目前仅Chrome、Edge等Chromium内核浏览器支持,如需更广兼容性,建议结合服务器主动推送方案。
2. dev-sw.js的注册逻辑为何没在源码中找到?
开发环境下,@vite-pwa/nuxt会通过Nuxt内部插件逻辑动态注入Service Worker注册脚本,而非直接写入静态HTML源码。你可以在浏览器Elements标签页的<head>区域找到动态添加的注册脚本;dev-sw.js是模块为开发环境生成的临时Service Worker,用于缓存调试,生产环境会替换为你自定义的文件。
3. 如何配置injectManifest模式?
在nuxt.config.ts的PWA配置中指定strategy: 'injectManifest',并自定义Service Worker的路径即可,示例配置如下:
export default defineNuxtConfig({ modules: ['@vite-pwa/nuxt'], pwa: { strategy: 'injectManifest', srcDir: 'public', // 自定义Service Worker所在目录 filename: 'sw.js', // 你的自定义Service Worker文件名 injectRegister: 'script', // 生产环境会自动在HTML头部注入注册脚本 manifest: { // 你的PWA Manifest配置(图标、名称等) name: 'My Nuxt PWA', short_name: 'Nuxt PWA', start_url: '/', display: 'standalone', background_color: '#ffffff', theme_color: '#41b883', icons: [ { src: '/icons/icon-192x192.png', sizes: '192x192', type: 'image/png' } ] }, workbox: { skipWaiting: true, // 新Service Worker激活后立即接管页面 clientsClaim: true } } })
自定义Service Worker实现推送的完整步骤
1. 编写自定义Service Worker文件
在public目录下创建sw.js,添加推送相关逻辑:
// 监听服务器推送事件 self.addEventListener('push', (event) => { const data = event.data?.json() || { title: '系统通知', body: '你有一条新消息' } const options = { body: data.body, icon: '/icons/icon-192x192.png', badge: '/icons/badge-72x72.png', data: { url: data.url || '/' } } event.waitUntil( self.registration.showNotification(data.title, options) ) }) // 监听通知点击事件 self.addEventListener('notificationclick', (event) => { event.notification.close() event.waitUntil( clients.openWindow(event.notification.data.url) ) }) // 注册定时同步任务(示例:24小时触发一次) async function registerPeriodicSync() { try { await self.registration.periodicSync.register('periodic-notification', { minInterval: 24 * 60 * 60 * 1000 }) } catch (err) { console.log('定时同步注册失败:', err) } } self.addEventListener('activate', (event) => { event.waitUntil(registerPeriodicSync()) }) // 处理定时同步事件 self.addEventListener('periodicsync', (event) => { if (event.tag === 'periodic-notification') { event.waitUntil( // 可在此请求后端获取最新通知内容,再触发推送 fetch('/api/get-notification') .then(res => res.json()) .then(data => { if (data.title && data.body) { self.registration.showNotification(data.title, { body: data.body }) } }) ) } })
2. 客户端请求通知权限并注册推送订阅
在plugins目录创建pwa.client.ts(仅在客户端运行):
export default defineNuxtPlugin(async () => { if ('serviceWorker' in navigator && 'PushManager' in window) { const registration = await navigator.serviceWorker.ready // 请求用户通知权限 const permission = await Notification.requestPermission() if (permission === 'granted') { // 生成推送订阅(需替换为你的VAPID公钥) const subscription = await registration.pushManager.subscribe({ userVisibleOnly: true, applicationServerKey: '你的VAPID公钥' }) // 将订阅信息发送到后端保存,用于服务器主动推送 await fetch('/api/save-subscription', { method: 'POST', body: JSON.stringify(subscription), headers: { 'Content-Type': 'application/json' } }) } } })
3. 生成VAPID密钥对
使用web-push库生成密钥对(用于服务器与客户端的推送鉴权):
- 安装依赖:
npm install web-push - 生成密钥:
const webpush = require('web-push') const vapidKeys = webpush.generateVAPIDKeys() console.log('VAPID公钥:', vapidKeys.publicKey) console.log('VAPID私钥:', vapidKeys.privateKey)
将公钥填入客户端代码,私钥保存在后端用于发送推送请求。
内容的提问来源于stack exchange,提问作者Moglash

