PWA Offline-First 是一套用于构建离线优先渐进式 Web 应用(PWA)的开发技能。据资料,它由 Peter Bamuhigire 分享。
用途 该技能适用于构建离线优先的 PWA,覆盖 Service Worker 生命周期、Workbox 缓存策略、通过 Dexie.js 使用 IndexedDB、用于排队写入的 Background Sync、Web Push 通知、Lighthouse 门槛以及 Next.js PWA 集成。它是必须在 EDGE/2G 或间歇性连接下工作的应用的默认方案。
参考资料
- references/tooling-and-tests.md:完整的 Vite + next-pwa 配置、Lighthouse CI 工作流、Playwright 离线测试脚手架。
- references/safari-webkit-27-pwa-checks.md:Safari/WebKit 27、iOS/iPadOS PWA 限制、Customizable Select、img sizes="auto"、布局变更、HTML <model> 以及 Web 扩展关注项。
产出证据 类别为正确性,产物为离线旅程与恢复测试包,格式为 Markdown 加浏览器/测试输出,示例涵盖缓存版本、离线操作、排队同步、冲突、更新、存储压力与恢复。
输入与依赖
- API、错误与幂等契约:由 api-design-first 产出,排队写入时必需,定义安全重放与冲突处理。
- 数据分类与保留规则:由安全/产品负责人提供,必需,控制浏览器存储与缓存。
- 离线关键流程清单:由产品负责人提供,必需,确定哪些功能必须在无连接时可用。
决策规则
- 哈希化不可变资源:缓存优先,带有限过期。
- 导航或可刷新读取:网络优先,带经过测试的离线回退。
- 变更、认证或支付:仅网络传输;在允许时持久化单独的幂等发件箱。
- 涉及金钱或库存的冲突:服务器权威解决。
领域反模式
- 缓存 POST 响应。修复:改为排队幂等变更记录。
- 在没有更新 UX 的情况下调用 skipWaiting()。修复:协调激活并安全重载。
- 将 navigator.onLine 视为服务器可达性。修复:处理真实请求失败与强制门户。
- 在 Cache Storage 中存储令牌或敏感负载。修复:最小化存储并应用数据策略。
- 测试离线读取而不重放写入。修复:验证保存、重启、重连、去重与冲突路径。
PWA 检查清单
- 通过 HTTPS 提供(localhost 豁免);Web App Manifest 存在并从 <head> 链接。
- Service Worker 在所需的最窄作用域注册。
- 图标为 192x192、512x512 及 maskable 变体。
- 捕获 beforeinstallprompt 并作为应用内安装按钮呈现。
- Lighthouse PWA 分数大于或等于 90。
- 核心内容在禁用 JavaScript 时渲染;交互功能优雅降级。
- start_url 离线时返回 HTTP 200(从缓存提供)。
Web App Manifest 示例包含 name、short_name、id、start_url、scope、display、orientation、theme_color、background_color、lang、icons 与 shortcuts 字段,并从 head 链接:<link rel="manifest" href="/manifest.webmanifest">。
应用安装条件 Chromium 浏览器对安装提示有一组固定条件:通过 HTTPS 提供;Manifest 包含 short_name 或 name;图标包含 192px 与 512px;Manifest 声明 start_url;display 为 fullscreen、standalone、minimal-ui 或 window-controls-overlay 之一;prefer_related_applications 缺失或为 false;用户至少点击或轻触页面一次并浏览至少 30 秒;Web 应用尚未安装。其他浏览器应用类似条件但有细微差异。可捕获并延迟提示,从自有 UI 触发。
Service Worker 生命周期 Service Worker 观察三个生命周期阶段:Download、Install、Activate。据 MDN:如果是首次提供 Service Worker,则尝试安装,安装成功后激活。activate 事件通常是清理旧缓存及上一版本相关内容的良好时机。功能事件(fetch、push)等待传给 event.waitUntil() 的 promise。浏览器在作用域内导航时检查更新的 worker,或如果过去 24 小时内未下载,则在任何 worker 事件时检查。状态:installing -> installed (waiting) -> activating -> activated。新版本会等待所有旧 worker 标签页关闭,除非调用 skipWaiting()。
Workbox 设置 Vite 使用 vite-plugin-pwa,Next.js 使用 next-pwa。默认使用 generateSW;仅在需要自定义 Service Worker 逻辑时切换到 injectManifest。完整的 Vite 与 Next.js 配置见 references/tooling-and-tests.md。
缓存策略
- CacheOnly:静态、版本化资源,直到 SW 更新才刷新;TTL 至下次部署。
- NetworkOnly:需要新鲜度的内容(HTML、变更),离线非优先;从不缓存。
- CacheFirst:不可变资源——字体、哈希化 JS/CSS、版本化图片;TTL 30 天。
- NetworkFirst:在线时需要最新版本的 HTML 或 API,带离线回退;TTL 10 分钟。
- StaleWhileRevalidate:偶尔更新的内容(头像、帮助文档),速度优先于新鲜度;TTL 24 小时。
永不缓存变更动词。Workbox 仅在明确要求时缓存 POST 响应;这样做几乎总是 bug。
缓存失效 每次部署时提升预缓存清单修订号;对于运行时缓存,在 cacheName 中嵌入构建哈希,使一次部署即可失效所有内容。Workbox 预缓存提供 cleanupOutdatedCaches(),它添加一个 activate 事件监听器,清理旧版 Workbox 创建的不兼容预缓存。从 Service Worker 入口调用一次。在每个运行时路由上使用 workbox-expiration 的 ExpirationPlugin({ maxEntries, maxAgeSeconds }) 以限制低端 Android 设备上的磁盘使用。
App Shell 架构 Shell 是渲染 UI 外壳所需的最小 HTML、CSS 和 JavaScript。安装时预缓存;内容在运行时获取并分层加入。
IndexedDB 与 Dexie.js IndexedDB 是用于客户端存储大量结构化数据(包括文件/blob)的低级 API——一个事务性数据库系统,具有限定为只读或读写事务的异步读写。模式迁移在 IDBOpenDBRequest.onupgradeneeded 上触发的 IDBVersionChangeEvent 内运行。适用同源策略。
存储对比
- IndexedDB:用于大型或结构化数据、blob、索引查询;限制为浏览器管理的配额(通常数百 MB);异步、事务性。
- localStorage:用于微小键值对、功能标志、最后路由;约 5 MB 每源;同步,阻塞主线程。
- Cache Storage:用于 HTTP 请求/响应对(Workbox 运行时缓存);浏览器管理;由 Service Worker 拥有。
原始 IndexedDB 冗长且易泄漏事务。Dexie 用 promise、类型化表和迁移封装它。安装:npm install dexie。
离线表单提交 先写入 IndexedDB。网络是尽力而为的附加项。用户立即看到“已保存”;Service Worker 在重连时排空队列。
Background Sync API 先进行特性检测;在没有 Background Sync 的平台上回退到 online 监听器,包括 Background Sync 不可用或受限的 Safari/WebKit 配置。
冲突解决
- 最后写入胜出——简单;可接受于非财务字段(备注、地址更正)。客户端时间戳覆盖服务器。
- 服务器权威——服务器以 HTTP 409 + 规范记录拒绝陈旧写入。用于金钱、库存数量、MoMo 引用。
- 基于时间戳合并——每个字段携带 updatedAt;每字段较新值胜出。用于两个离线客户端编辑同一记录。
金钱默认采用服务器权威:账本是唯一事实来源。
离线优先 UX 模式
- 连接感知 UI:读取 navigator.onLine 并监听 online/offline 事件以切换横幅。对于粗略带宽检测,检查 navigator.connection.effectiveType('2g'、'3g'、'4g');在 Network Information API 不可用时优雅回退。
- 乐观更新:写入 IndexedDB 并立即反映在 UI 中;通过 Background Sync 排队网络同步;将行标记为 pending 直到 SW 确认服务器 ack。
适用场景 资料强调东非(乌干达、肯尼亚、坦桑尼亚)连接呈双峰分布:城市光纤与 4G,以及用户登上 boda、进入合作社或医院病房时的 EDGE/2G。现场工作人员、社区卫生工作者、确认移动货币 disbursement 的代理都容忍数分钟无信号。每个写入路径必须本地排队并在连接恢复时同步;每个读取路径必须回退到缓存响应而非加载指示器。
限制 资料未提供关于收费、价格、平台支持范围或效果保证的更多信息。