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

React PWA多环境下firebase-messaging-sw.js动态配置问询

React PWA 多环境接入FCM推送的Service Worker落地方案

问题核心

开发支持多环境配置的React PWA应用时,通过多份.env文件(.env.sit、.env.development、.env.performance、.env.production)注入对应环境的Firebase配置,接入Firebase Cloud Messaging推送时存在两个互斥问题:

  • firebase-messaging-sw.js放在public目录下时,文件不会经过webpack编译,无法读取构建阶段注入的process.env环境变量,只能硬编码配置,无法随环境自动切换
  • 将文件迁移到src目录时,React默认的构建规则会把打包后的js文件放到build/static/js子目录下,Service Worker无法部署在站点根路径,浏览器的sw作用域限制会导致推送逻辑完全不生效

注:浏览器规定Service Worker只有部署在站点根路径(或响应头手动配置Service-Worker-Allowed)时,才能接管全站的推送、消息事件,这是硬限制,无法绕过。

实现思路

不需要强行把sw文件直接从src目录原封不动发布,而是把src作为sw模板的存放位置,通过构建前脚本自动注入当前环境的配置值,生成符合sw运行规范的最终文件,输出到public目录和构建产物的根目录,既可以让配置随环境动态切换,又能保证sw部署在正确的路径。

具体操作步骤

  • 第一步:抽离前台Firebase配置
    在src目录下编写Firebase配置模块,读取process.env里的所有REACT_APP_FIREBASE_*变量,供前台业务代码初始化messaging实例使用,这部分逻辑和普通React业务代码没有区别,原有src目录下的配置代码可以直接使用,不需要修改。
  • 第二步:编写sw模板文件
    在src目录下新建firebase-messaging-sw.js作为模板,注意Service Worker运行在独立Worker线程,不能直接import npm包形式的firebase,必须用importScripts引入官方CDN资源,配置项用%变量名%格式的占位符代替硬编码值:
importScripts("https://www.gstatic.com/firebasejs/8.6.1/firebase-app.js")
importScripts("https://www.gstatic.com/firebasejs/8.6.1/firebase-messaging.js")

firebase.initializeApp({
  apiKey: "%REACT_APP_FIREBASE_API_KEY%",
  authDomain: "%REACT_APP_FIREBASE_AUTH_DOMAIN%",
  databaseURL: "%REACT_APP_FIREBASE_DATABASE_URL%",
  projectId: "%REACT_APP_FIREBASE_PROJECT_ID%",
  storageBucket: "%REACT_APP_FIREBASE_STORAGE_BUCKET%",
  messagingSenderId: "%REACT_APP_FIREBASE_MESSAGING_SENDER_ID%",
  appId: "%REACT_APP_FIREBASE_APP_ID%",
  measurementId: "%REACT_APP_FIREBASE_MEASUREMENT_ID%",
})

const messaging = firebase.messaging()

messaging.setBackgroundMessageHandler(function (payload) {
  const notificationTitle = payload.data.title
  const notificationOptions = {
    body: payload.data.body,
    icon: "./assets/manifest/x48.png",
  }
  return self.registration.showNotification(
    notificationTitle,
    notificationOptions,
  )
})

self.addEventListener("notificationclick", event => {
  event.notification.close()
  event.waitUntil(
    self.clients.matchAll({type: 'window'}).then(windowClients => {
      for (let i = 0; i < windowClients.length; i++) {
        const client = windowClients[i]
        if ('focus' in client) return client.focus()
      }
      if (self.clients.openWindow) return self.clients.openWindow('/')
    })
  )
})
  • 第三步:编写自动生成sw的构建脚本
    在项目根目录新建scripts/generate-fcm-sw.js,逻辑是读取src下的模板,替换所有环境变量占位符,把最终文件输出到对应目录:
const fs = require('fs')
const path = require('path')

// 读取src下的sw模板
const templatePath = path.resolve(__dirname, '../src/firebase-messaging-sw.js')
let swContent = fs.readFileSync(templatePath, 'utf-8')

// 替换所有REACT_APP开头的环境变量占位符
Object.keys(process.env).forEach(key => {
  if (key.startsWith('REACT_APP_')) {
    const placeholder = `%${key}%`
    swContent = swContent.replaceAll(placeholder, process.env[key] || '')
  }
})

// 输出到public目录,供本地开发环境使用
const publicSwPath = path.resolve(__dirname, '../public/firebase-messaging-sw.js')
fs.writeFileSync(publicSwPath, swContent)

// 构建完成后输出到build根目录,供生产部署
const buildPath = path.resolve(__dirname, '../build')
if (fs.existsSync(buildPath)) {
  const buildSwPath = path.resolve(buildPath, 'firebase-messaging-sw.js')
  fs.writeFileSync(buildSwPath, swContent)
}
  • 第四步:配置package.json执行脚本
    修改package.json里的命令,保证启动开发服务、执行构建前自动跑生成sw的脚本,不同环境的构建命令正常用env-cmd加载对应.env文件即可:
{
  "scripts": {
    "start": "node scripts/generate-fcm-sw.js && react-scripts start",
    "build": "react-scripts build && node scripts/generate-fcm-sw.js",
    "build:sit": "env-cmd -f .env.sit npm run build",
    "build:development": "env-cmd -f .env.development npm run build",
    "build:performance": "env-cmd -f .env.performance npm run build",
    "build:production": "env-cmd -f .env.production npm run build"
  }
}

避坑提示

  • public目录下自动生成的firebase-messaging-sw.js要加入.gitignore,避免把带具体环境配置的文件提交到代码仓库,所有修改只需要操作src下的模板文件即可
  • 注册Service Worker时,sw路径直接写/firebase-messaging-sw.js,不要加static之类的子路径
  • 不要在sw文件里用ES Module的import/export语法,Worker环境默认不支持,直接用importScripts引入外部资源是兼容性最好的方案
  • 如果升级Firebase SDK版本,记得同步修改模板里importScripts的CDN版本号,和项目里安装的firebase版本保持一致

内容的提问来源于stack exchange,提问作者Tamil Vanan

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.28 07:01:00