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

添加firebase包导致部署在Firebase的Next.js SSR崩溃

问题描述

我有一个部署在Firebase上的Next.js项目,包含一个SSR页面,原本通过以下代码加载firebase-admin SDK运行正常:

export const getServerSideProps: GetServerSideProps = async ({..}) => {
  console.log("Init Firebase admin App");
  if (admin.apps.length == 0) {
    admin.initializeApp();
  }

但在package.json中添加"firebase": "^10.8.0"依赖后,部署到Firebase的站点在SSR过程中崩溃,报错:

The default Firebase app does not exist. Make sure you call initializeApp() before using ...

日志显示崩溃前已经打印了Init Firebase admin App。

补充最小复现示例(MRE):本地运行Next.js一切正常,但部署到Firebase后,根路径/可正常访问,/invite/1234会崩溃返回500错误。

相关代码如下:

package.json

{
  "name": "echo-photos",
  "version": "0.0.1",
  "private": true,
  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start",
    "lint": "next lint"
  },
  "dependencies": {
    "eslint-config-next": "^14.1.0",
    "firebase": "^10.8.1", // 移除这一行后问题解决
    "firebase-admin": "^11.4.1",
    "next": "^14.1.0",
    "react": "^18.2.0",
    "react-hooks-global-state": "^2.0.0",
    "typescript": "^4.9.5"
  },
  "devDependencies": {
    "@next/swc-darwin-arm64": "^13.4.2",
    "@types/node": "18.11.9",
    "@types/react": "^18.2.8",
    "@types/react-dom": "^18.2.19",
    "@types/react-router-dom": "^5.3.3",
    "autoprefixer": "^10.4.13",
    "postcss": "^8.4.21",
    "tailwindcss": "^3.2.7"
  }
}

pages/_app.tsx

import type { AppProps } from "next/app";
import Head from "next/head";
import "../styles/style.css";

function App({ Component, pageProps }: AppProps) {
  return (
    <div>
      <Head>
        <title>Echo Photos</title>
      </Head>
      <Component {...pageProps} />
    </div>
  );
}

export default App;

// pages/index.tsx
export default function HomePage() {
  return (
    <>
        Hello Home
    </>
  );
}

pages/invite/[id].tsx

import Head from "next/head";
import { useRouter } from "next/router";
import { GetServerSideProps } from "next";
import admin from "firebase-admin";

interface PropsData {
  domain: string;
}

export const getServerSideProps: GetServerSideProps = async ({
  req,
}) => {
  if (admin.apps.length == 0) {
    admin.initializeApp();
  }
  const host = req.headers.host;
  const projectId = admin.instanceId().app.options.projectId;

  let domain = `https://${projectId}.web.app`;
  if (host?.includes("localhost") || host?.includes("127.0.0.1")) {
    domain = "https://echo-photos-dev.web.app";
  }

  let propsData: PropsData = {
    domain: domain,
  };

  return {
    props: propsData,
  };
};

export default function InvitePage(props: PropsData) {
  const router = useRouter();
  const fullInviteId = router.query.id as string;
  const inviteCode = fullInviteId.substring(0, 8);

  return (
    <>
      <Head>
        <title>
          Title Example
        </title>
      </Head>

      <section>
        Hello "{props.domain}" with code {inviteCode}
      </section>
    </>
  );
}
原因分析与解决方案

核心原因

这是**firebase客户端SDK和firebase-admin服务端SDK在Firebase托管环境中的命名空间冲突**导致的:

  • 同时安装两个SDK后,Firebase托管环境会优先加载客户端SDK的全局变量,导致服务端代码中导入的firebase-admin对象或其内部实例检测逻辑被意外干扰。
  • 虽然代码执行了initializeApp(),但由于冲突,初始化的实例并未正确绑定到admin对象,后续调用admin.instanceId()时就会提示默认应用不存在。
  • 本地开发环境无此问题,是因为Next.js本地服务端运行环境和Firebase托管的隔离机制不同,不会触发这种冲突。

解决方案

方案1:抽离服务端SDK初始化逻辑

将firebase-admin的初始化单独放在服务端专用文件中,确保仅在服务端加载:

  1. 创建lib/firebase-admin.ts:
import admin from "firebase-admin";

// 确保只初始化一次
if (!admin.apps.length) {
  admin.initializeApp();
}

export const adminDb = admin;
  1. 在pages/invite/[id].tsx中替换导入:
// 替换原有的import admin from "firebase-admin"
import { adminDb } from "../../lib/firebase-admin";

export const getServerSideProps: GetServerSideProps = async ({ req }) => {
  // 使用adminDb替代admin
  const projectId = adminDb.instanceId().app.options.projectId;
  // ...其他逻辑保持不变
};

方案2:动态导入服务端SDK

在getServerSideProps中动态导入firebase-admin,避免客户端打包时包含它,绕开命名空间冲突:

export const getServerSideProps: GetServerSideProps = async ({ req }) => {
  // 动态导入确保仅在服务端加载
  const admin = await import("firebase-admin");
  
  if (!admin.apps.length) {
    admin.initializeApp();
  }
  
  const host = req.headers.host;
  const projectId = admin.instanceId().app.options.projectId;
  // ...其他逻辑不变
};

方案3:标记客户端SDK为服务端外部依赖(不推荐)

如果不需要在服务端使用客户端SDK,可通过next.config.js配置将firebase标记为服务端外部依赖:

/** @type {import('next').NextConfig} */
const nextConfig = {
  serverComponentsExternalPackages: ['firebase'],
}

module.exports = nextConfig

此方案稳定性不如前两种,优先推荐方案1或2。


内容的提问来源于stack exchange,提问作者Nicolas Degen

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.28 20:55:58