如何在Next.js公共目录的Firebase Cloud Messaging Service Worker中使用环境变量
在Next.js中为公共目录的Service Worker注入环境变量
我完全懂你的困扰——公共目录下的静态文件会被Next.js原样复制到输出目录,根本不会经过Webpack的编译流程,所以process.env变量自然没法被替换成实际的环境值。下面给你两种我在项目里实际用过的可行方案:
方案一:构建时动态生成Service Worker文件
这是最直接稳定的方法,通过Node脚本在开发或构建前自动生成带有环境变量的firebase-messaging-sw.js,而且生成的文件可以加入.gitignore,完全不用在Git里管理敏感配置。
具体步骤:
- 在项目根目录创建
scripts文件夹,新建generate-sw.js脚本:
const fs = require('fs'); const path = require('path'); // 用环境变量拼接Service Worker的完整内容 const swContent = ` importScripts('https://www.gstatic.com/firebasejs/8.8.1/firebase-app.js'); importScripts('https://www.gstatic.com/firebasejs/8.8.1/firebase-messaging.js'); firebase.initializeApp({ apiKey: "${process.env.NEXT_PUBLIC_API_KEY}", authDomain: "${process.env.NEXT_PUBLIC_AUTH_DOMAIN}", projectId: "${process.env.NEXT_PUBLIC_PROJECT_ID}", storageBucket: "${process.env.NEXT_PUBLIC_STORAGE_BUCKET}", messagingSenderId: "${process.env.NEXT_PUBLIC_MESSAGING_SENDER_ID}", appId: "${process.env.NEXT_PUBLIC_APP_ID}", measurementId: "${process.env.NEXT_PUBLIC_MEASUREMENT_ID}", }); firebase.messaging(); firebase.messaging().setBackgroundMessageHandler((payload) => console.log('payload', payload)); `.trim(); // 将内容写入public目录的目标文件 const targetPath = path.join(__dirname, '../public/firebase-messaging-sw.js'); fs.writeFileSync(targetPath, swContent);
- 修改
package.json的脚本,让开发和构建前自动执行这个生成脚本:
"scripts": { "predev": "node scripts/generate-sw.js", "dev": "next dev", "prebuild": "node scripts/generate-sw.js", "build": "next build", "start": "next start" }
这里用
predev和prebuild前缀是Next.js的特性——它会在执行dev或build命令前自动运行带对应前缀的脚本。
- 把生成的
public/firebase-messaging-sw.js加入.gitignore,避免提交到Git:
# .gitignore public/firebase-messaging-sw.js
这样每次启动开发服务或构建项目时,脚本都会读取当前环境变量,生成最新的Service Worker文件,完美解决公共目录无法使用环境变量的问题。
方案二:客户端传递配置给Service Worker
如果不想用构建脚本,也可以通过客户端传递配置的方式实现,但步骤稍复杂,且需要处理好时机问题:
- 在客户端(比如
pages/_app.js)初始化Firebase时,准备好配置并注册Service Worker,之后发送配置消息:
import { initializeApp } from "firebase/app"; import { getMessaging } from "firebase/messaging"; const firebaseConfig = { apiKey: process.env.NEXT_PUBLIC_API_KEY, authDomain: process.env.NEXT_PUBLIC_AUTH_DOMAIN, projectId: process.env.NEXT_PUBLIC_PROJECT_ID, storageBucket: process.env.NEXT_PUBLIC_STORAGE_BUCKET, messagingSenderId: process.env.NEXT_PUBLIC_MESSAGING_SENDER_ID, appId: process.env.NEXT_PUBLIC_APP_ID, measurementId: process.env.NEXT_PUBLIC_MEASUREMENT_ID, }; // 初始化客户端Firebase const app = initializeApp(firebaseConfig); // 注册Service Worker并发送配置 navigator.serviceWorker.register('/firebase-messaging-sw.js') .then(registration => { // 确保Service Worker激活后再发消息 if (registration.active) { registration.active.postMessage({ type: 'SET_FIREBASE_CONFIG', config: firebaseConfig }); } else { // 监听Service Worker激活事件 registration.addEventListener('updatefound', () => { const newWorker = registration.installing; newWorker.addEventListener('statechange', () => { if (newWorker.state === 'activated') { newWorker.postMessage({ type: 'SET_FIREBASE_CONFIG', config: firebaseConfig }); } }); }); } });
- 修改Service Worker,监听消息并初始化Firebase:
importScripts('https://www.gstatic.com/firebasejs/8.8.1/firebase-app.js'); importScripts('https://www.gstatic.com/firebasejs/8.8.1/firebase-messaging.js'); // 监听来自客户端的配置消息 self.addEventListener('message', (event) => { if (event.data.type === 'SET_FIREBASE_CONFIG') { const firebaseConfig = event.data.config; firebase.initializeApp(firebaseConfig); firebase.messaging(); firebase.messaging().setBackgroundMessageHandler((payload) => console.log('payload', payload)); } });
这种方法的缺点是:如果Service Worker在客户端发送消息前就完成了初始化,会导致配置加载失败,需要额外处理激活时机问题。所以相比之下,方案一更省心可靠。
内容的提问来源于stack exchange,提问作者Wataru Ono
相关产品推荐
相关产品推荐

