Firebase 缓存与部署后强制刷新

部署完用户还看到旧页?用这份 firebase.json 缓存头配置 + curl 诊断 + 回滚流程,几秒内把陈旧内容刷掉。

你部署了。*.web.app 上新版本已经在跑。朋友打开自定义域名,看到的还是昨天的标题。基本都是缓存的问题——Firebase 的 CDN、浏览器,或者两者一起。改一个 firebase.json 里的 cache header 再部署,就修好了。

问题背景

Firebase Hosting 在 CDN 边缘缓存资源,同时让浏览器也缓存。没显式 Cache-Control 的文件默认大约 1 小时。这对图片 / JS 没什么问题,但对 HTML 是灾难——访客要等一小时才能看到更新。正确做法是在 firebase.json 里按文件类型配置缓存头,外加 service worker 自律(如果你有的话)。

判断标准

  • 部署成功了,新内容只有无痕窗口能看到。
  • 手机看到新的、电脑看到旧的(或反过来)——浏览器缓存状态不同。
  • 自定义域名是旧内容,*.web.app 是新——边缘缓存按 host 区分。
  • 老的 service worker 一直返回缓存里的旧资源。
  • curl -I 看 HTML URL 没有 cache-control 头(用了默认)。

快速结论

HTML 设 Cache-Control: no-cache, max-age=0,带 hash 的静态资源设 max-age=31536000, immutable,然后部署一次——下一个请求就拿到新版本。

开始前准备

  • 构建输出目录要和 firebase.json 对得上(Astro 是 dist,Next 静态导出是 out)。
  • 装好 curl 验证响应头——不要只看 devtools,它自己也有缓存行为。
  • 有 service worker 的话先找到源文件,再改缓存。

实操步骤

  1. 先诊断。 看慢路径上的缓存状态:
curl -sI https://yourdomain.com/ | grep -iE 'cache-control|age|x-cache'
# 典型坏态:
#   age: 1840
#   x-cache: HIT
#   (没有 cache-control 或写着 'public, max-age=3600')
  1. firebase.json——生产可用完整配置:
{
  "hosting": {
    "public": "dist",
    "ignore": ["firebase.json", "**/.*", "**/node_modules/**"],
    "cleanUrls": true,
    "trailingSlash": true,
    "headers": [
      {
        "source": "**/*.html",
        "headers": [
          { "key": "Cache-Control", "value": "no-cache, max-age=0" }
        ]
      },
      {
        "source": "/_astro/**",
        "headers": [
          { "key": "Cache-Control", "value": "public, max-age=31536000, immutable" }
        ]
      },
      {
        "source": "**/*.@(js|css|woff2)",
        "headers": [
          { "key": "Cache-Control", "value": "public, max-age=31536000, immutable" }
        ]
      },
      {
        "source": "**/*.@(jpg|jpeg|png|webp|avif|svg)",
        "headers": [
          { "key": "Cache-Control", "value": "public, max-age=2592000" }
        ]
      },
      {
        "source": "/sitemap*.xml",
        "headers": [
          { "key": "Cache-Control", "value": "public, max-age=3600" }
        ]
      }
    ]
  }
}
  1. HTML 用 no-cache, max-age=0,不是 no-store no-cache 是”存一份但每次都 revalidate”,浏览器还能用本地副本,只是每次都查新鲜度。no-store 强制完整重新下载,浪费。

  2. 带 hash 的静态资源加 immutable 只有文件名随内容变才能用。Astro 的 /_astro/** 和 Vite 的 hashed 输出都符合;平常的 app.js 不符合。

  3. 部署后验证边缘头:

npm run build
firebase deploy --only hosting

# 30 秒后:
curl -sI https://yourdomain.com/                          | grep -i cache-control
# cache-control: no-cache, max-age=0

curl -sI https://yourdomain.com/_astro/index.abc123.css   | grep -i cache-control
# cache-control: public, max-age=31536000, immutable
  1. **浏览器卡住的话,硬刷一次(Cmd+Shift+R / Ctrl+Shift+R)**清本地缓存。之后 no-cache 头就会保证一直是新的。

  2. 有 service worker 的,每次部署 ship 新版本 SW 并 skipWaiting()——否则陈旧缓存能存一周:

// public/sw.js
const VERSION = 'v2026-05-22-1';                 // 每次部署递增

self.addEventListener('install', (e) => {
  self.skipWaiting();                            // 立刻激活
});

self.addEventListener('activate', (e) => {
  e.waitUntil(
    caches.keys().then((keys) =>
      Promise.all(keys.filter((k) => !k.includes(VERSION)).map((k) => caches.delete(k)))
    )
  );
  self.clients.claim();
});
  1. 某条路径 CDN 边缘还是陈旧时,要么改一下 URL 强制 miss(带版本路径 + redirect),要么 release 来回滚一遍:
firebase hosting:releases:list
firebase hosting:clone yourproject:live yourproject:live --version <prev>
firebase deploy --only hosting   # 再前进一次,边缘重新分发

执行检查清单

  • HTML、hashed 资源、图片、sitemap 在 firebase.json 里都有显式 Cache-Control
  • 每次部署后 curl -sI 验证响应头。
  • service worker(如果有)每次部署递增版本号 + skipWaiting()
  • build 产物的 /_astro/** 或等价目录文件名带 hash。

上线后验证

  • 用不同网络(或 WebPageTest 这种远程工具)curl -sI——确认边缘缓存确实更新了,不是只你本地节点。
  • 无痕 + 未登录浏览器都立刻看到新版本。
  • DevTools → Application → Service Workers 显示新版本已激活。

容易踩的坑

  • HTML 留默认约 1 小时缓存,每次部署后追”残影”追一小时。
  • HTML 上设 immutable——浏览器永远不再 revalidate,硬刷都没用。
  • 框架的 cache header 和 firebase.json 冲突——后写的赢,devtools 或 curl 看实际响应。
  • service worker 有 bug,部署后永远返回旧 bundle。
  • 改完配置后 CDN 边缘缓存还在——等 5 分钟或 release 回滚再前进强制重发。
  • 本想用 no-cache 却写成 no-store——no-store 把 back/forward cache 也关了。

FAQ

  • 怎么让所有人立刻刷新?: 改一下 HTML 重新部署。边缘缓存按新版本失效,浏览器有长缓存的就用 no-cache 触发 revalidate。
  • 部署时 Firebase 会清 CDN 缓存吗?: 会,针对受影响的路径。新内容大多数地区几秒内传到边缘。
  • 不同路径能设不同缓存吗?: 能。headers 数组支持多个 source 模式,每个独立配——同一个 header name 由首条匹配胜出。
  • 页面新了 CSS 还旧——为啥?: CSS 文件名可能没带 hash。要么 build 时加 hash,要么把 CSS 缓存时间调短。
  • no-cache 会损性能吗?: 对静态 HTML 影响小。浏览器还是用本地副本走 conditional GET(If-None-Match),网络往返很轻,频繁 revalidate 是换”即时更新”的代价。

相关阅读

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