你部署了一个 Firebase Cloud Function,从前端调用:
const result = await httpsCallable(functions, 'sendEmail')({to: 'x'});
报:
FirebaseError: Function not found: sendEmail
或者直接 HTTP fetch 一个 trigger:
404 Not Found
但 Firebase Console 里明明能看到这个函数列表里有 sendEmail。这是 Cloud Functions 最让人困惑的错——console 里”看得见”不等于客户端”调得到”。问题永远在 name / region / deploy state 三处之一。
常见原因
按命中率从高到低:
1. Region 不匹配(客户端和函数)
最高频。函数代码声明了 region: 'asia-east1',但客户端 SDK 没显式指定 region,默认连 us-central1——结果在 us-central1 里找不到这个函数。
如何判断:
- 函数代码:
onCall({ region: 'asia-east1' }, ...)或老语法functions.region('asia-east1').https.onCall(...) - 客户端:
getFunctions(app)不带 region 参数 = us-central1 - 不一致 → not found
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 upload” 或 “skipped”。
4. 用了 v1 / v2 SDK 混乱
Firebase Functions 现在分 v1 (firebase-functions) 和 v2 (firebase-functions/v2)。两版部署到不同 endpoint,客户端调用方式也微妙不同。
如何判断:检查 import:import { onCall } from 'firebase-functions/v2/https'(v2)vs import * as functions from 'firebase-functions'(v1)。
5. 函数被 GC 了(debug 部署)
如果你 deploy 时减少了导出(exports.sendEmail 这次没 export),Firebase 会自动删除它。下次 deploy 再 export 但忘了……函数还没在。
如何判断:firebase functions:list 看是否真的存在。
6. CORS 让请求根本没到 server
HTTPS triggers 有 CORS 限制。前端跨域调用如果没设 CORS,浏览器直接拦截,看起来像 not found 但其实没发出去。
如何判断:浏览器 Network 看请求 status 是不是 0 或 CORS error,而不是 404。
最短修复路径
Step 1:先 list 一下确认存在
firebase functions:list --project my-project-prod
# 输出示例:
# ┌──────────────┬─────────┬───────────────┬─────────┐
# │ Function │ Version │ Trigger │ Region │
# ├──────────────┼─────────┼───────────────┼─────────┤
# │ sendEmail │ v2 │ https │ us-east1│
# └──────────────┴─────────┴───────────────┴─────────┘
确认函数名、版本、region。
Step 2:客户端 SDK 显式设 region
// v2 SDK
import { getFunctions, httpsCallable } from 'firebase/functions';
// ❌ 不指定 = us-central1
const fns = getFunctions(app);
// ✅ 跟函数一致
const fns = getFunctions(app, 'us-east1');
const callSendEmail = httpsCallable(fns, 'sendEmail');
Step 3:函数代码 region 显式声明
// v2
import { onCall } from 'firebase-functions/v2/https';
export const sendEmail = onCall(
{ region: 'us-east1', cors: true }, // 显式 region
async (req) => { /* ... */ }
);
Step 4:重新 deploy 看 build 日志
firebase deploy --only functions:sendEmail --debug 2>&1 | tee deploy.log
# 搜关键词
grep -i "error\|failed\|skipped" deploy.log
如果 build 失败,CLI 会显示但容易被淹在长输出里。--debug 模式更详细。
Step 5:local emulator 验证
firebase emulators:start --only functions
# 客户端改连 emulator
import { connectFunctionsEmulator } from 'firebase/functions';
if (location.hostname === 'localhost') {
connectFunctionsEmulator(fns, 'localhost', 5001);
}
emulator 上正常调用 = 部署 / region 问题;emulator 上也 not found = 代码问题。
Step 6:直接 HTTP curl
# v2 onCall endpoint
curl -X POST https://us-east1-my-project.cloudfunctions.net/sendEmail \
-H "Content-Type: application/json" \
-d '{"data":{"to":"x"}}'
# 404 = 真的不存在或 region 错
# 200 / 401 / 403 = 存在但其他问题(auth / permission)
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) => ...);
两者 URL 路径不同,混用会调错地方。统一用一个版本。
预防建议
- 项目里只用一个 region,写进 CLAUDE.md / README,新函数都用同一个
- 客户端 SDK 初始化时一定显式传 region,不要靠默认值
- 把 region 抽成 constant:
export const FUNCTIONS_REGION = 'us-east1',client + server 都引用 - deploy 完跑一个 health check 脚本,依次 curl 所有 endpoint
- CI 加 typecheck 步骤,确保 build 失败不会跳过部署
- 函数名用 camelCase(matches JS export),不要混 kebab-case
- v1 → v2 迁移时全量改,别一半 v1 一半 v2
- 监控加 “function not found” 报警,量上去早发现