Firebase Function not found:修复 region / 名字 / 部署不一致(2026)

Console 里明明有,调用却 not found。几分钟内排查 region、函数名和部署状态。

你部署了一个 Firebase Cloud Function,从前端调用:

const result = await httpsCallable(functions, 'sendEmail')({ to: 'x' });

报错通常是下面之一:

FirebaseError: Function not found: sendEmail
FirebaseError: internal

或者直接 HTTP fetch 返回:

404 Not Found

但 Firebase Console 里明明能看到 Functions 列表里有 sendEmail。这是 Cloud Functions 最让人困惑的错:console 里”看得见”不等于这个客户端”调得到”。原因几乎永远是三者之一——region、名字、部署状态。

最快修复(约 70% 命中): 函数实际运行的 region 跟客户端 SDK 指向的不是同一个。先跑 firebase functions:list 读出真实 region,再把这个 region 原样传给客户端的 getFunctions(app, 'us-east1')。其他情况见下文。

你属于哪一类?

先跑一条命令,再对症:

firebase functions:list --project my-project-prod
现象最可能的原因跳转
列表里有这个函数,但 region 跟客户端设的不一样Region 不匹配Step 2
列表里根本没有这个函数部署被跳过 / build 失败 / 被 GCStep 4
列表里的名字和你调用的字符串大小写/拼写不同函数名不一致原因 2
浏览器 console 报 CORS error、status 0 或 preflight 被拦是 CORS,不是函数不存在原因 6
curl 返回 200/401/403,但 SDK 调用失败v1/v2 路径或 auth 问题,不是 not foundStep 6

常见原因

按命中率从高到低。

1. Region 不匹配(客户端和函数)

最高频。函数声明了 region: 'asia-east1',但客户端 SDK 没指定 region。JS 客户端 SDK 在你调用 getFunctions(app)(不带 region 参数)时仍然默认连 us-central1——于是它去 us-central1 找函数,可函数不在那。

最近的 Firebase CLI 版本有一个变化经常坑人:CLI 不再无条件部署到 us-central1。它会尽量根据你的项目配置和触发数据源(Firestore、Storage)的 region 来推断部署位置。所以函数可能落在一个你从没显式选过的 region,而客户端还在硬默认 us-central1。永远以 functions:list 读到的 region 为准,别凭假设。

如何判断

  • 函数代码:onCall({ region: 'asia-east1' }, ...)(v2)或老语法 functions.region('asia-east1').https.onCall(...)(v1)
  • 客户端:getFunctions(app) 不带第二个参数 = 落到 us-central1
  • v2 callable 的 region 不匹配经常表现为 CORS / preflight 错误或 FirebaseError: internal,而不是干净的 “not found”——因为 SDK 打到了一个不存在的 URL,preflight 拿不到有效响应。别被措辞误导。

2. 函数名拼写 / 大小写不一致

// 函数代码
export const sendEmail = onCall(...);

// 客户端
httpsCallable(functions, 'send-email')  // 错:用了 kebab-case
httpsCallable(functions, 'sendmail')    // 错:拼错

Firebase 函数名就是 export 的变量名——严格区分大小写,必须完全一致。

如何判断:从 firebase functions:list 或 console 复制确切函数名,跟客户端调用里的字符串逐字对照。

3. Build 静默失败,没真部署

CLI 打印了 Deploy complete!,但其中一个函数因为 TypeScript 错误或 missing dependency 被跳过了。其他函数好了,这个静默没上去。

如何判断firebase deploy --only functions --debug,在输出里搜 Failed to uploadskipped,或者靠近顶部的 TypeScript 编译错误。

4. 用了 v1 / v2 SDK 混乱

Firebase Functions 分 v1(firebase-functions)和 v2(firebase-functions/v2)。两版部署到不同 endpoint,调用方式也微妙不同。v2 底层跑在 Cloud Run 上,这也是为什么 v2 函数会拿到两个 URL(详见 Step 6)。

如何判断:看 import——import { onCall } from 'firebase-functions/v2/https'(v2)vs import * as functions from 'firebase-functions'(v1)。

5. 函数被上一次部署删掉了(garbage collection)

如果上一次 deploy 没 export sendEmail,Firebase 会当它被移除并从项目里删掉。之后你再 export 回来,但 deploy 还没跑完(或失败了),函数就真的不在。

如何判断firebase functions:list 看它现在到底存不存在。

6. CORS 让请求根本没离开浏览器

HTTPS callable trigger 只有在配置了 CORS 时才会自动处理 CORS preflight。v2 要在函数 options 里设 cors: true(或显式的来源白名单)。不设的话,前端跨域调用会被浏览器在客户端直接拦掉——看起来像 404,但其实从没到 server。

如何判断:浏览器 Network 标签里看到 status 0 或明确的 CORS policy 错误(例如:Access to fetch at 'https://us-central1-my-project.cloudfunctions.net/sendEmail' from origin 'https://my-project.web.app' has been blocked by CORS policy),而不是函数返回的干净 404。

最短修复路径

Step 1:先 list 确认存在

firebase functions:list --project my-project-prod

# 输出示例:
# ┌──────────────┬─────────┬───────────────┬─────────┐
# │ Function     │ Version │ Trigger       │ Region  │
# ├──────────────┼─────────┼───────────────┼─────────┤
# │ sendEmail    │ v2      │ https         │ us-east1│
# └──────────────┴─────────┴───────────────┴─────────┘

这一条同时确认三件事:确切函数名、generation(v1/v2)、region。如果列表里没有 sendEmail,直接跳到 Step 4——你的问题是部署,不是 region。

Step 2:客户端 SDK 显式设 region

// v2 SDK
import { getFunctions, httpsCallable } from 'firebase/functions';

// 错:不带 region 参数 = 落到 us-central1
const fns = getFunctions(app);

// 对:跟 Step 1 里的 region 一致
const fns = getFunctions(app, 'us-east1');
const callSendEmail = httpsCallable(fns, 'sendEmail');

getFunctions 的第二个参数可以是 region(如 us-east1),也可以是托管 callable 的自定义域名。用 Step 1 里看到的那个确切 region 字符串。

Step 3:函数代码里显式声明 region

// v2
import { onCall } from 'firebase-functions/v2/https';

export const sendEmail = onCall(
  { region: 'us-east1', cors: true },  // 显式 region + CORS
  async (req) => { /* ... */ }
);

在代码里钉死 region,部署就不会悄悄换地方(见原因 1 里 CLI 自动选 region 的行为)。

Step 4:重新 deploy 并真正看 build 日志

firebase deploy --only functions:sendEmail --debug 2>&1 | tee deploy.log

# 搜那些藏在长输出里的失败信号
grep -i "error\|failed\|skipped\|not deployed" deploy.log

Deploy complete! 并不能证明某个具体函数上去了。CLI 确实会报 build 失败,但在长输出里一刷就过去了;--debug 配合 tee 让你能回头搜。

Step 5:用 local emulator 验证

firebase emulators:start --only functions
// 客户端改连 emulator 而不是生产环境
import { connectFunctionsEmulator } from 'firebase/functions';
if (location.hostname === 'localhost') {
  connectFunctionsEmulator(fns, '127.0.0.1', 5001);
}

emulator 上能调通、生产上不行 = 部署或 region 问题;emulator 上也 not found = 代码问题。(如果你的运行时把 localhost 解析成 IPv6 而 emulator 只绑了 IPv4,用 127.0.0.1 而不是 localhost。)

Step 6:直接 curl 这个 endpoint

v2 函数会暴露两个 URL:一个确定性的 cloudfunctions.net URL,和一个带随机 hash 的 Cloud Run *.a.run.app URL。cloudfunctions.net 这个手动拼起来最简单:

# v2 onCall endpoint(确定性 URL)
curl -X POST https://us-east1-my-project.cloudfunctions.net/sendEmail \
  -H "Content-Type: application/json" \
  -d '{"data":{"to":"x"}}'

# 404                = 真的不存在,或 URL 里 region 错了
# 200 / 401 / 403    = 存在;你的问题是 auth/permission,不是 not found

callable 要求 POST body 包在 data key 里,就像上面这样。如果 curl 通了但 SDK 不通,说明函数没问题,问题回到客户端的 region 或函数名不一致。

Step 7:v1 / v2 混用检查

// v1 写法
import * as functions from 'firebase-functions';
export const sendEmail = functions.https.onCall((data, context) => { /* ... */ });

// v2 写法
import { onCall } from 'firebase-functions/v2/https';
export const sendEmail = onCall((req) => { /* ... */ });

两个 generation 解析到不同的 URL 路径,混用就会调错地方。整个仓库统一用一个 generation。新函数都从 v2 起步,老的 v1 等有空再迁。

怎么确认修好了

  1. firebase functions:list 显示 sendEmail 在预期的 region 和 generation 上。
  2. 客户端的 getFunctions(app, '<region>') 里是同一个 region 字符串。
  3. 确定性 URL 的 curl 返回 200(或 401/403,同样证明它存在)。
  4. 前端调用不再出现 Function not foundinternal 或 Network 标签里的 CORS error。

四条都过,not found 就是真的没了——剩下的是 auth 或业务逻辑,不再是路由问题。

预防建议

  • 一个项目只用一个 region。写进 README,新函数都用它。
  • 客户端 SDK 初始化时一定显式传 region,不要靠默认值。
  • 抽成常量:export const FUNCTIONS_REGION = 'us-east1';,client 和 server 都引用。
  • deploy 后跑一个 health check,依次 curl 所有 endpoint,非 2xx/4xx 就让 CI 失败。
  • CI 加 typecheck 步骤,让 TypeScript 错误没法混进一次”成功”的部署。
  • 函数名用 camelCase 并跟 JS export 一致,别混 kebab-case。
  • v1 迁 v2 时全量一次改完,别一半 v1 一半 v2。
  • 每次升级 CLI 后重新核对部署的 region——最近几个版本改了默认 region 的行为。

常见问题

为什么 console 里看得到函数,客户端却找不到? console 列出的是所有 region 里部署的全部函数。客户端 SDK 只在你配置的那一个 region(或默认 us-central1)里找。console 里看得见、但对客户端是错的 region = not found。

我没设过 region,函数到底部署到哪了? 较新的 Firebase CLI 会根据项目配置和触发源推断 region,而不是一律用 us-central1。跑 firebase functions:list 看它实际落在哪,并在代码里钉死 region,让它别再乱跑。

我的报错是 internal 或 CORS error,不是 “Function not found”,是同一个问题吗? 常常是。指向错误 region 的 v2 callable 会打到一个不存在的 URL,失败的 preflight 表现为 CORS error 或 FirebaseError: internal,而不是干净的 not found。先修 region,再去追 CORS。

curl 返回 200,但 SDK 还是失败,为什么? 说明函数存在且可达,不是部署或路由问题。重新核对传给 getFunctions 的 region,以及 httpsCallable 里那个确切的名字字符串——客户端就剩这两个开关了。

callable 函数需要设 CORS 吗? v2 onCall 需要在函数 options 里设 cors: true(或显式来源列表),这样 SDK 的 preflight 才能从你的网页来源通过。v1 callable 是自动处理的;v2 要你显式声明。

相关阅读

外部参考:Cloud Functions locations · Call functions from your app

标签: #后端 #排查 #排查 #Firebase