首页 / 站务日志

Blog · 2026-10-18

PWA 不是加个 manifest 就完事

让一个静态站「能装、能离线」只需要一个 manifest 加一个 service worker,但要让它在手机上真的像应用,坑全在后面那几十行。

给站点加 PWA 的理由很朴素:工具站 139 个页面全在浏览器里跑,手机上每次打开都要重新下载 CSS 和 JS,地铁里一断网就白屏。装上 manifest 与 service worker 之后,它可以被添加到主屏、断网也能打开、首屏直接走缓存。但真正动手才发现,能装和能用是两件事。

第一坑:scope 写错,装完打开的是别人的首页

工具站部署在 /Timarsky/ 子路径下,它的 manifest 里 start_url 与 scope 必须都限制在子路径内,否则从主屏图标点进来会跳到 xkmyth.github.io/ —— 用户看到的是主站首页,会以为自己装错了东西。所以工具箱单独有一份 manifest(/Timarsky/tools/manifest.webmanifest),主站与工具站各用自己的 scope,互不越界。

第二坑:一种缓存策略套全站,总有一半是错的

最初想简单点:全部 cache-first,反正静态站不会变。结果是改完样式刷新看不到效果,得手动清缓存。现在按资源分三类:

  • 导航请求(HTML):network-first,带超时;拿不到就回缓存,缓存也没有就回 /404.html。
  • 静态资源(CSS / JS / 图标):stale-while-revalidate —— 先用缓存让页面秒开,后台再更新。
  • service worker 自身:no-store,永远从网络取,否则新版 SW 永远装不上。

这三条加起来不到四十行,但它决定了「改完之后多久生效」这个每天都在发生的问题。

第三坑:更新时机比更新本身更难

新版本 SW 装好后调用 skipWaiting() + clients.claim(),页面会立刻用上新代码——听起来很棒,直到你在备忘应用里写了半屏文字,后台更新把它刷掉了。所以这里选择「等下个导航再生效」:SW 里不主动接管,用户下次打开或跳页时自然切到新版本。代价是更新晚一次点击生效,换来的是正在编辑的内容不会凭空消失。

离线兜底的顺序

断网时请求进来,先看缓存,再看 404 兜底页。404 页不是白屏,它写了三张可点的卡片(回首页、看工具目录、去 Releases),因为一个人看到「没网」的时候,最需要的是一条能走通的路,而不是一句报错。

现在主站与工具站都能装、能离线,缓存的都是自家资源,没有第三方 CDN 参与 —— 这一点和「零上传」是同一条原则的两面:不依赖别人的服务器,就不用担心别人什么时候关掉它。

这一条对应哪份文档:工程侧的取舍记在 路线图,可复用的经验记在 文档,改动的具体版本见 版本日志。