Next.js 12集成Firebase v9 FCM报messaging.onMessage不是函数错误
问题原因
你遇到的TypeError: messaging.onMessage is not a function报错来自两个核心问题:
- 你使用的是Firebase v9及以上的模块化SDK,该版本中
onMessage不是messaging实例的挂载方法,需要单独从firebase/messaging包导入后调用 - 代码存在隐藏逻辑bug:
getApps是函数,你写的!getApps.length没有执行函数,判断逻辑永远不成立,会重复初始化Firebase应用;另外Next.js 12默认带SSR,FCM依赖浏览器专属API,逻辑如果在服务端执行会返回不符合预期的实例。
修复方案
1. 修正Firebase初始化和导入逻辑
首先调整导入项,补上缺失的onMessage,修正Firebase应用初始化的判断逻辑:
import { initializeApp, getApp, getApps } from "firebase/app" // 新增单独导入onMessage import { getMessaging, getToken, onMessage } from "firebase/messaging" import { firebaseConfig } from "./config" // 修正getApps调用,加括号执行函数 const app = !getApps().length ? initializeApp(firebaseConfig) : getApp()
2. 修正FCM方法逻辑,增加客户端环境判断
所有FCM相关逻辑都要判断是否在浏览器环境运行,避免SSR阶段触发报错,同时修正onMessage的调用方式:
// 补充token存在性判断逻辑,和你原有逻辑对齐即可 const isTokenAvailable = () => { if (typeof window === 'undefined') return null return localStorage.getItem("fcm_token_prac") } export const cloudMessaging = async () => { // 服务端渲染阶段直接返回,不执行后续逻辑 if (typeof window === 'undefined') return { status: false } const token = isTokenAvailable() if (token !== null) { return { status: true, token: token, } } try { const permission = await Notification.requestPermission() const messaging = getMessaging(app) if (permission === "granted") { const FCM_TOKEN = await getToken(messaging, { vapidKey: process.env.NEXT_PUBLIC_FCM_VAPID_KEY, }) if (FCM_TOKEN) { localStorage.setItem("fcm_token_prac", FCM_TOKEN) return { status: true, token: FCM_TOKEN, } } } return { status: false } } catch (err) { console.log(err, "cloudmessaging error") return { status: false, } } } export const onMessageListener = () => { // 服务端环境直接返回空 if (typeof window === 'undefined') return Promise.resolve(null) const messaging = getMessaging(app) return new Promise((resolve) => { // 调用导入的独立onMessage函数,传入messaging实例 onMessage(messaging, (payload) => { resolve(payload) }) }) }
3. 组件侧调用说明
你在Layout组件里的useEffect调用逻辑不需要大改,useEffect本身只会在客户端执行,不会触发SSR问题。如果开了React严格模式导致重复监听,可以加一个监听标记避免重复绑定:
useEffect(() => { let mounted = true async function firebaseInit() { try { await cloudMessaging() } catch (err) { console.log(err) } } firebaseInit() return () => { mounted = false } }, []) useEffect(() => { let mounted = true onMessageListener() .then((payload) => { if (mounted && payload) { console.log(payload, "onMessageListener") // 这里写你的前台通知展示逻辑 } }) .catch((err) => console.log(err, "onMessageListener useEffect")) return () => { mounted = false } }, [])
注意事项
- 必须将Firebase官方要求的
firebase-messaging-sw.js服务工作者文件放在Next.js项目的public目录下,否则后台推送无法接收,令牌获取也可能出现异常 NEXT_PUBLIC_FCM_VAPID_KEY必须是Firebase控制台「云消息传递」板块生成的网页推送证书密钥,不要填错- 本地调试时请使用localhost或者HTTPS协议访问,否则浏览器会拦截通知权限请求
内容的提问来源于stack exchange,提问作者Arijit Maiti
相关产品推荐
相关产品推荐

