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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.28 02:24:21