Firebase Hosting 部署后路由 404:5 个原因和修法

本地能开、部到 Firebase Hosting 就 404。用官方请求优先级顺序、firebase.json 片段和 curl 命令,十分钟定位原因。

本地跑得好好的。firebase deploy 一发,开线上 URL,礼貌地 404。Firebase Hosting 的 404 几乎都是五种原因之一,知道是哪种之后大约十分钟能修好。关键是别再瞎猜:本地复现一次、拿 firebase.json 对照 build 产物、用 curl 看响应,而不是看一个被缓存毒化的浏览器标签页。

一句话结论

  • 先复现:npm run build && firebase serve --only hosting,再 curl -sI http://localhost:5000/about/。这里 404 说明是 build 问题;返回 200 说明是部署或配置问题。
  • 最常见的两个原因:firebase.jsonpublic 目录配错,以及 cleanUrls/trailingSlash 和 framework 真实产出的文件名对不上。
  • 经典 Hosting 不会自动 SSR。需要服务端渲染的路由得 rewrite 到 Cloud Function 或 Cloud Run,或者把这部分迁到 Firebase App Hosting。
  • 残留的 ** 兜底 rewrite 到 /index.html 会把每个 404 都伪装成首页 HTML。内容站要删掉它。
  • Firebase Hosting 的 Spark 套餐免费、允许商用,所以这些修法都不需要升级付费。

Firebase 是怎么决定返回什么的

Firebase Hosting 基于开源引擎 Superstatic,每个请求按一套固定优先级处理。搞清楚这个顺序,就知道你的 404 到底卡在哪。根据官方配置文档,顺序是:

  1. 保留的 /__/* 命名空间(Firebase 自己的路径)。
  2. 配置的 redirects,第一条匹配的规则生效。
  3. public 目录里精确匹配的静态文件。
  4. 配置的 rewrites,第一条匹配的规则生效。
  5. 你的自定义 404.html(如果有)。
  6. Firebase 默认 404 页。

所以一个 404 意味着请求一路走到了第 6 步:没有静态文件匹配,也没有 rewrite 接住。下面每个修法都是为了让请求落到第 3 或第 4 步。

是哪种原因?

现象最可能的原因
部分路由能开、部分 404trailingSlash / cleanUrls 不一致(原因 2)
只有 / 能开、其它都 404public 目录配错(原因 1)
只有该 SSR 的路由 404缺 rewrite 到 Function / Cloud Run(原因 3)
只有今天新加的路由 404build 没跑,或部署了旧产物(原因 4)
/_astro/... 等带 hash 的资源 404build 产物里缺对应 hash 文件(原因 5)

动手改之前先本地复现

这一步把每个 404 都切成「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 配置 / 部署问题(文件在)

curl -sI(如果同时跑 Functions,就用 firebase emulators:start),这样浏览器缓存完全不掺和进来。一个在 curl 里复现不了的失败 URL,通常是缓存遗留,不是真的 404。

原因 1:public 指向了错误的目录

firebase init 默认把 public 设成 public/,但大多数 framework 输出到别处:Astro 和 Vite 输出到 dist/,Next.js 静态导出输出到 out/public 配错,上面的第 3 步就永远匹配不上,除了碰巧的 /index.html,每条路由都 404。

{
  "hosting": {
    "public": "dist",
    "ignore": ["firebase.json", "**/.*", "**/node_modules/**"],
    "cleanUrls": true,
    "trailingSlash": true
  }
}

确认这条路由的文件确实在那个目录里:

ls -la dist/about/
# index.html   ← 有
# (空)        ← framework 没生成该路由
find dist -name 'about*' -type f
# dist/about.html        ← 配 cleanUrls: true
# dist/about/index.html  ← 配 trailingSlash: true

原因 2:cleanUrls / trailingSlash 对不上

这个最隐蔽,会出现经典的「一半页面能开」现象。这两个设置是 301 跳转规则,不只是好看:

  • cleanUrls: true 把已存在页面的 .html 后缀去掉,并把任何 /about.html 请求 301 跳到 /about。截至 2026 年 6 月,它只对存在的页面生效;不存在的页面必须精确命中某条 redirects 规则。
  • trailingSlash: true 用 301 跳转补上末尾斜杠;trailingSlash: false 则跳转去掉斜杠。

把设置和 framework 写到磁盘上的文件名对齐。四个合法组合:

framework 产出               firebase.json                                       线上 URL
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,所以要配 trailingSlash: true。一个值得点名的坑:trailingSlash: falsecleanUrls: false 这个组合曾经在 Superstatic 里触发无限跳转,没测过就别用这对组合。

原因 3:SSR 路由没有 rewrite

经典 Firebase Hosting 只服务静态文件,不跑你的服务端代码,所以任何该服务端渲染的路由,如果不 rewrite 到后端就会 404。截至 2026 年 6 月你有两条路。

保留经典 Hosting,加一条 rewrite 到 Cloud Function 或 Cloud Run:

{
  "hosting": {
    "rewrites": [
      { "source": "/api/**", "function": "api" },
      { "source": "/render/**", "run": { "serviceId": "ssr-render", "region": "us-central1" } }
    ]
  }
}

rewrite 写了仍然 404 或 Function not found 的常见坑:

  • serviceIdregion 和已部署服务对不上。跑 firebase functions:list(或去 Cloud Run 看),确认两个字段都精确一致。
  • 后端根本没部署。两个一起部署:firebase deploy --only functions,hosting。Cloud Run 要先部署服务、再部署 Hosting 配置,这样 rewrite 才有活的目标可指。
  • 对外公开的 Cloud Run 服务需要 --allow-unauthenticated,否则调用方拿到的是报错而不是你的页面。

或者迁到 Firebase App Hosting——它在 2025 年 4 月正式 GA,现在是 Google 为 Next.js、Angular 等 SSR framework 推荐的落脚点。注意:旧的 Hosting「web frameworks」实验已对新的 Next.js 用户关闭,所以新的 SSR 项目应该直接从 App Hosting 起步,而不是手写一堆 rewrite。

原因 4:你部署了旧产物

如果只有今天新加的路由 404,多半是 build 没跑,或者 CI 部署了旧的 dist/。确认线上 release 和你最新一次 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,可与列表对照

修法几乎都是 CI 顺序问题:确保流水线先 npm run buildfirebase deploy,并且部署的是 build 输出目录、不是源码。

原因 5:带 hash 的资源 404

/_astro/... 等内容 hash 文件 404,说明部署的 HTML 引用了一个上传包里没有的 hash,通常是部分部署,或者 CDN 缓存了旧的 index.html、它指向的文件名已被新 build 改掉。重新跑一次干净 build 加完整部署,测试时绕开缓存:

curl --header 'Cache-Control: no-cache' -sI https://yourdomain.com/_astro/<file>.js | head -3

那条藏住所有 404 的兜底 rewrite

留意这条遗留规则,SPA 模板里很常见:

{ "source": "**", "destination": "/index.html" }

它会静默地把每个没匹配上的路径都变成首页 HTML。技术上不算 404,但它会毁掉 SEO 和深链,因为每个 URL 都返回同一份壳。Astro 和大多数静态内容 framework 都不需要它。内容站要删掉;只有真正客户端路由的 SPA 才保留。

验证修好了

  • 之前 404 的每个 URL 现在 curl -sI 都返回 200。
  • 故意访问一个不存在的 URL 仍然返回 404、不是 200,证明你的 404.html 生效。
  • Search Console URL Inspection 对受影响页面显示 “URL is on Google” 或至少 “Page fetched”。

常见问题

/ 能开、/about 404 为什么? 十有八九是 trailingSlashcleanUrls。要么文件叫 about.html 但没开 cleanUrls,要么文件是 about/index.htmltrailingSlashfalse。把设置和 build 写到磁盘的文件名对齐就行。

该开 cleanUrls 吗? 大多数内容站建议开:URL 更干净,还会帮你把 .html 形式 301 跳转过去。只要内部链接和这个设置保持一致,别串成连环跳转就好。

我的路由本该 SSR,为什么 404? 经典 Firebase Hosting 不会自动渲染。你需要把该路径 rewrite 到 Cloud Function 或 Cloud Run,或者把应用放到 Firebase App Hosting。如果 firebase.json 里 rewrite 配了,响应仍然是 404 或「function not found」,几乎都是 serviceId/区域和已部署后端对不上(Firebase function not found)。

rewrite 看起来没问题,但同一个 URL 仍然走静态文件。 同路径有真实文件遮挡,因为精确匹配的静态内容(第 3 步)在 rewrite(第 4 步)之前评估。这条规则也可能排在更宽泛的规则下面。按rewrite 没触发排查。

dist/ 里能看到文件,线上还是 404? 多半 deploy 没把它上传上去。用 firebase hosting:releases:list 确认,把时间戳和你最近一次 build 对照;CI 可能跳过了 build,或者部署了错误的目录。

修这个需要 Blaze 套餐吗? 不需要。Firebase Hosting 的 Spark 套餐免费、允许商用,所以静态内容的 404 修法不花钱。只有加 Cloud Functions 或 Cloud Run 做 SSR 时才需要 Blaze。

相关阅读

标签: #独立开发 #Firebase #部署 / 托管 #排查