Firebase Hosting 路由 404 怎么办:SPA / SSG / 子路径 全场景修复

部署到 Firebase Hosting 后首页正常,但访问 `/about` 或子页面就 404?这是 firebase.json 的 rewrites 没配好。本文按 SPA、SSG、静态多页站、Astro / Next 各场景给出修复方案。

部署到 Firebase Hosting 后,首页打开是好的,但访问 /about/blog/xxx 或刷新某个子页面就显示 404—— 这是 Firebase Hosting 几乎所有人都遇到的坑。原因不在 Firebase,而在你 firebase.jsonrewrites 配错或没配。

先 5 秒判断

  • 首页 OK,刷新子页面 404 → firebase.json 缺 SPA fallback rewrite
  • 子页面打开 OK,刷新还是 OK,只有某些 URL 404 → SSG 漏了静态文件
  • 部分语言路径全部 404(例如 /en/ 全部不行)→ 缺路径前缀的 index.html
  • 第一次部署就所有页都 404 → 部署目录写错(public vs dist

SPA 应用:必须加 rewrite to /index.html

Vue / React / SvelteKit-SPA / Vite SPA,路由是前端 React Router / Vue Router 处理的。访问 /about 时,Firebase 找不到 /about.html,就 404。

修复方法

{
  "hosting": {
    "public": "dist",
    "ignore": ["firebase.json", "**/.*", "**/node_modules/**"],
    "rewrites": [
      {
        "source": "**",
        "destination": "/index.html"
      }
    ]
  }
}

注意 "public" 字段要写你 build 后的实际目录:Vite/Vue/React 通常是 dist,CRA 是 build

SSG 应用(Astro / Next.js static export / Hugo)

SSG 会为每个页面生成对应的 .html,例如 /about/index.html。一般不需要 SPA fallback。但有几个坑:

坑 1:Astro 输出”trailing slash”不一致

Astro 默认输出 /about/index.html,你访问 /about(不带斜杠)有时直接 404。Firebase Hosting 默认会做”添加斜杠”的 301,但有些边缘情况失败。

修复:在 astro.config.mjs 中:

export default defineConfig({
  trailingSlash: 'always',
  build: { format: 'directory' }
});

并确保 build 后目录里每个页面都是 subdir/index.html 形式。

坑 2:Next.js static export 路径错位

next build && next export 后默认会有 out/about.html 而不是 out/about/index.html。Firebase 把 /about 当目录找 index 找不到,就 404。

修复:在 next.config.js

module.exports = {
  output: 'export',
  trailingSlash: true,
};

坑 3:动态路由没生成

Next.js 的 [slug].tsx 或 Astro 的 [slug].astro 必须在 build 时枚举所有路径,否则那些 URL 在 SSG 输出里根本不存在。

修复

  • Astro:导出 getStaticPaths()
  • Next.js:导出 getStaticPaths 并 return 所有 paths

静态多页站(每个页面一个 HTML)

最简单的场景。firebase.json 通常不需要 rewrites:

{
  "hosting": {
    "public": "site",
    "cleanUrls": true,
    "trailingSlash": true
  }
}

cleanUrls/about.html 可以用 /about 访问,trailingSlash 强制统一。

子路径 / 多语言部署

例如 /zh//en/ 都要正常打开:

  • 必须确保 public/zh/index.htmlpublic/en/index.html 都存在
  • 如果是 SPA 多语言(前端路由),要为每个语言加一条 fallback:
"rewrites": [
  { "source": "/zh/**", "destination": "/zh/index.html" },
  { "source": "/en/**", "destination": "/en/index.html" },
  { "source": "**", "destination": "/index.html" }
]

部署目录写错了

非常常见的低级错误:

  • firebase.json"public": "public",但你的框架输出在 dist/
  • 部署后 Firebase 只看到 public/ 里的 placeholder 内容,所有真实页面都 404

修复:把 "public" 改成 build 实际输出目录。

部署前的自检清单

按顺序检查:

  1. firebase.json"public" 指向 build 真正输出目录
  2. 该目录下确实有 index.html 和子目录页面
  3. SPA 项目:有 rewrites 兜底 /index.html
  4. SSG 项目:trailingSlash 配置一致
  5. 多语言子路径:每个子路径都有 index.html
  6. firebase deploy --only hosting 后看输出的”X files deployed”是不是合理数量

最短修复路径

按命中率排:

  1. 加 SPA fallback rewrite —— 修 80% 的子页 404
  2. 打开 build 目录看文件是否存在 —— 修剩下 15%
  3. 检查 public 字段 —— 修最低级但最致命的 5%

什么时候不是你的问题

  • Firebase Hosting 全球确实偶尔故障(看 status.firebase.google.com
  • CDN 缓存延迟,刚 deploy 完 5 分钟内 404 可能是缓存
  • 自定义域名 DNS 还没生效(通常 24-48 小时)

如果 404 来自 rewrite 到函数的路径,那是另一层问题——参考rewrite 没触发排查规则顺序 / 静态文件遮挡,以及Firebase function not found排查 firebase.json 与已部署函数的区域 / 名称不匹配。

容易误判的情况

  • 以为 SSG 不用 rewrite —— 大部分情况是的,但 trailing slash 配置错时仍然 404
  • 以为部署成功就万事大吉 —— 部署成功只代表上传了文件,不代表路由对
  • 以为 emulator 跑通就线上跑通 —— firebase serve 和真实 Hosting 行为略有差异
  • 以为 CDN 没缓存 —— Firebase 默认会缓存 HTML,强刷不一定生效;用隐身窗口测

预防建议

  • 第一次部署先用 firebase hosting:channel:deploy preview 预览
  • firebase.json 一旦修改,部署前本地跑 firebase serve --only hosting
  • 不要把生成目录命名为 public/(容易跟 Firebase 默认混淆)
  • CI 里加一步:deploy 之后用 curl 检查几个关键路径返回 200

常见问题(FAQ)

Q:Astro 部署到 Firebase 一直 404 怎么办? A:80% 是 trailingSlash 设置和 firebase.jsoncleanUrls / trailingSlash 不一致。统一改成”always”或”never”,两边配置都对齐。

Q:Next.js 一定要 SSG export 才能用 Firebase Hosting 吗? A:纯 Hosting 只支持静态。需要 SSR 用 Firebase Functions / Cloud Run。Vercel 部署 Next.js 更省心。

Q:rewrite 和 redirect 有什么区别? A:rewrite 是”对外仍然是这个 URL,内部去拿另一个文件”;redirect 是”浏览器跳到新 URL”。SPA fallback 用 rewrite。

Q:部署后看到的还是旧页面? A:浏览器缓存或 Firebase 自己的 CDN 缓存。用隐身窗口、Cmd+Shift+R 强刷,或加 query 参数 ?v=1 绕过缓存。

Q:多个 hosting site 在同一个项目里怎么部署? A:firebase.json 用数组:每个 site 一项;部署用 firebase deploy --only hosting:siteId

相关问题

决策前的检查清单

  • 如果错误是在某次改动后立刻出现,先回滚或隔离那次改动,不要同时试一堆无关修复。
  • 如果只在生产环境出现,对比环境变量、build 产物、缓存、权限和平台设置。
  • 如果只影响某个账号或浏览器,优先查权限、cookie、插件、额度和地区可用性。
  • 如果有两个修复方向,先选最容易验证、最容易撤销的那个。

什么时候可以先停下来

当你无法复现、日志和 UI 互相矛盾、涉及账单或账号安全、或者每个修复都需要你没有的生产权限时,就该停止盲试并升级处理。向平台支持或同事求助前,把完整错误、时间点、项目 ID、复现步骤、截图和最近改动整理好。清楚的升级说明,通常比再猜一小时更快解决问题。

诊断流程

  1. 先复现一次问题,并写下准确路径。复现不了时,先收集证据,不要急着改设置。
  2. 判断影响范围:一个用户还是所有用户,一个浏览器还是全部浏览器,只在本地还是只在线上,新内容还是旧内容也受影响。
  3. 优先查最近一次改动。大多数排查不是寻找神秘根因,而是找出哪次改动制造了不一致。
  4. 把系统切成两半测:输入 vs 输出、本地 vs 线上、账号 vs 项目、源文件 vs 生成文件、prompt vs 模型。确认哪一半还在失败。
  5. 先做最小且可撤销的修复。不要同时改 DNS、权限、账单、部署和代码。
  6. 用原复现路径和一个相邻路径验证,再记录最终是哪一步修好的。

最小复现模板

问题:
- [完整错误或异常表现]

发生位置:
- URL / 工具 / 项目:
- 账号:
- 环境:local / preview / production
- 浏览器 / 设备:

复现步骤:
1.
2.
3.

预期结果:
- 

实际结果:
- 

最近改动:
- 代码:
- 配置:
- DNS / 权限 / 账单:
- Prompt / 模型 / 上传文件:

证据:
- 截图:
- Console error:
- 服务端或平台日志:

这些”假修复”别做

  • 只清缓存,却不确认底层文件、权限、路由或设置是否正确。
  • 明明是环境变量、凭证、额度或平台配置问题,却反复重装依赖。
  • 一次改好几个无关设置,最后不知道到底是哪一步起作用。
  • 从另一个框架或平台复制修复方法,却不确认路由、build 输出或鉴权模型是否相同。
  • 没看 status page 和近期反馈,就把平台临时故障当成自己的 bug。

标签: #Firebase #部署 / 托管 #路由 404 #排查