本地跑得好好的。一部署,开线上 URL,礼貌地 404。Firebase Hosting 的 404 几乎都是四种原因之一,知道是哪种之后十分钟能修好——前提是你本地复现、对照 firebase.json、并真去看 build 产物。
问题背景
Firebase Hosting 从 firebase.json 配的 public 目录里找文件。请求进来先做文件匹配,再应用 cleanUrls 和 trailingSlash 转换,最后查 rewrite。都匹配不上就 404。“本地行线上不行”基本都是 build 产物和 Firebase 实际服务的内容之间有差。
判断标准
- 部分路由能开部分 404——基本是 trailing slash /
cleanUrls不一致。 - 只有
/能开其它都 404——public目录配错了。 - 只有需要 SSR 的路由 404——缺一条 rewrite 到 Cloud Function 或 Cloud Run。
- 只有今天新加的路由 404——build 没跑或者部署了旧产物。
/_astro/...资源 404——build 产物里缺对应 hash 文件(缓存不匹配)。
快速结论
先用 firebase serve 本地复现一次。本地也复现,问题在 build;只有线上 404,问题在 firebase.json 或部署步骤。
开始前准备
firebase-tools已装,能跑firebase serve --only hosting。firebase.json和 build 输出目录都能看到。- 失败 URL 用
curl能稳定复现(排除浏览器缓存)。
实操步骤
- 本地复现。 一招就能定位 build 类问题:
npm run build
firebase serve --only hosting
# 另一个 shell:
curl -sI http://localhost:5000/about/ | head -3
# HTTP/1.1 404 ... ← build 输出问题
# HTTP/1.1 200 ... ← Firebase 配置 / 部署问题
firebase.json的 “public” 和真实 build 目录对得上。 Astro →dist,Next 静态 →out,Vite →dist:
{
"hosting": {
"public": "dist",
"ignore": ["firebase.json", "**/.*", "**/node_modules/**"],
"cleanUrls": true,
"trailingSlash": true
}
}
常见坑:framework 输出 dist/,但 public 还留着 firebase init 默认的 public/。
- 确认 build 产物里真的有那个文件:
ls -la dist/about/
# index.html ← 有
# (空) ← framework 没生成该路由
find dist -name 'about*' -type f
# dist/about.html ← 预期 cleanUrls: true
# dist/about/index.html ← 预期 trailingSlash: 'always'
cleanUrls和trailingSlash必须与 framework 一致。 四个合法组合:
framework 产出 firebase.json
about.html cleanUrls: true, trailingSlash: false → /about
about.html + /about/ 跳转 cleanUrls: true, trailingSlash: false → /about
about/index.html cleanUrls: false, trailingSlash: true → /about/
about/index.html cleanUrls: true, trailingSlash: true → /about/ (最干净)
Astro build.format: 'directory' 产出 about/index.html——firebase.json 里要 trailingSlash: true。
- 该 SSR 的路由确认有 rewrite:
{
"hosting": {
"rewrites": [
{ "source": "/api/**", "function": "api" },
{ "source": "/render/**", "run": { "serviceId": "ssr-render", "region": "us-central1" } }
]
}
}
rewrite 写了还 404(或 Function not found),说明函数名 / 区域和已部署函数对不上——firebase functions:list 查一下。
- 小心兜底
**rewrite 到/index.html——这会把所有 404 静默吃掉,每个 URL 返回首页 HTML:
{ "source": "**", "destination": "/index.html" }
Astro 和大多数静态 framework 不需要这条——内容站务必删掉。
- 确认 deploy 真的把新 build 推上去了:
firebase hosting:releases:list
# release 2026-05-22 14:02 <hash> ← 时间戳对得上最近一次 build
curl -sI https://yourdomain.com/about/ | grep -i x-served-by
# 通常带 release hash 可对照
- 绕开缓存: 无痕窗口,或者
curl --header 'Cache-Control: no-cache'。
执行检查清单
firebase serve能复现同样的 404(或证明问题在平台侧)。firebase.json的 “public” 与 framework 实际输出目录一致。cleanUrls和trailingSlash与 framework 的文件命名匹配。firebase.json里没有遗留的**兜底 rewrite。- CI 工作流先
npm run build再firebase deploy。
上线后验证
- 之前 404 的所有 URL 都
curl -sI返回 200。 - Search Console URL Inspection 显示 “URL is on Google” 或至少 “Page fetched”。
- 故意访问不存在路径仍然返回 404,不是 200(证明 404 页生效)。
容易踩的坑
public指到源码目录——除了源码里有的路径全 404。- framework 和 host 的 trailing slash 不一致——页面在
/about/,但链接是/about,一半能开一半不行。 - 忘了自己加过
**rewrite 到/index.html——所有路由返回同一份 HTML(技术上不是 404 但效果一样)。 - CI 里没 build 就 deploy。
- CDN 缓存了旧的 404——清缓存或等待,文件其实在。
- 需要登录态的路由对游客 404——确认该路由是否需要会话。
FAQ
/能开/about404 为什么?: 十有八九是 trailing slash 或cleanUrls。要么文件叫about.html但没开cleanUrls,要么反过来。- 该开
cleanUrls吗?: 大多数站建议开——URL 更干净。但内部链接要和这个设置保持一致。 - 该 SSR 的路由 404 为什么?: Firebase Hosting 不会自动 SSR。需要加一条 rewrite 把该路径转到 Cloud Function 或 Cloud Run。
firebase.json里 rewrite 配了,但响应仍然是 404 或「function not found」,几乎都是函数名 / 区域和 rewrite 对不上(Firebase function not found)。 - rewrite 看起来没问题但同一个 URL 仍然走静态文件。: 同路径有真实文件遮挡,或这条规则排在更宽泛的规则下面。按rewrite 没触发排查。
dist/里能看到文件,线上还 404?: 确认 deploy 真的上传了:firebase hosting:channel:list看 release 内容。CI 可能没跑 build。