← 返回博客
Smartphone rendering an app interface while the network signal is unavailable, with cached data blocks behind it

PWA 离线支持怎么实现:能做什么、不能做什么(2026)

PWA 离线支持怎么实现:能做什么、不能做什么(2026)

"可以离线使用"是渐进式 Web 应用(PWA)最常被提起的卖点,也是最容易被误解的一个。PWA 离线支持并不意味着整个应用在断网时照常运行,它的真实含义是:你可以用 JavaScript 决定"请求到不了服务器时该发生什么"——而这份控制权有很清楚的边界。

本文讲清楚 PWA 离线支持的实现机制、四种值得掌握的缓存策略、离线模式真正做不到的事,以及上线前该怎么测。

PWA 离线支持到底指什么

普通网站在断网时没有任何应对手段,那一刻的控制权在浏览器手里,用户看到的是浏览器自带的错误页。

PWA 会注册一个 Service Worker——一段运行在页面背后的后台脚本,位于页面与网络之间。页面发出的每个请求都先经过它。当网络不可用时,Service Worker 可以改从本地缓存里取出响应,而不是让请求失败。

所以"离线支持"更准确的描述是:一层请求拦截 + 一层你自己编程管理的存储。注册了 Service Worker 并不会自动缓存任何东西。如果你没写缓存规则,装好的 PWA 断网时的表现和普通网页完全一样。

底层有两套存储在干活:

  • Cache Storage:存完整的 HTTP 响应(HTML、CSS、JS、图片、字体)。Service Worker 回放请求时读的就是它。
  • IndexedDB:存结构化数据(记录、草稿、待发送的操作)。用户在离线状态下填写的内容放这里。

Service Worker 是怎么把离线撑起来的

Service Worker 的生命周期分三个阶段,每一段都直接影响离线表现。

install(安装):浏览器拿到新版本 Service Worker 文件时触发一次。这里做"应用外壳"预缓存——渲染出可用界面所需的最小文件集:外壳 HTML、核心 CSS、主 JS 包、Logo,以及一个离线兜底页。

activate(激活):新 Worker 接管时触发。这里删除旧版本遗留的缓存。漏做清理是"PWA 连续几周吐旧资源"的头号原因。

fetch(请求):页面每发一个请求都会触发。你的处理函数在这里决定:走网络、读缓存、还是两者结合。离线支持真正发生的地方就是这里。

最小可用形态大致长这样:

const CACHE = 'shell-v3';
const SHELL = ['/', '/offline.html', '/app.css', '/app.js'];

self.addEventListener('install', e => {
  e.waitUntil(caches.open(CACHE).then(c => c.addAll(SHELL)));
});

self.addEventListener('activate', e => {
  e.waitUntil(
    caches.keys().then(keys =>
      Promise.all(keys.filter(k => k !== CACHE).map(k => caches.delete(k)))
    )
  );
});

self.addEventListener('fetch', e => {
  if (e.request.mode === 'navigate') {
    e.respondWith(fetch(e.request).catch(() => caches.match('/offline.html')));
  }
});

有三条约束建议一开始就记住:Service Worker 文件必须走 HTTPS(本地开发的 localhost 例外);它的作用域只覆盖自身所在目录及以下,放在 /js/sw.js 的 Worker 管不到 /;它跑在独立线程里、拿不到 DOM,只能通过消息与页面通信。

四种缓存策略,分别适合什么

真实项目里的离线方案几乎都是这四种模式的组合,而且是按请求类型分别选择,不是全站套一种。

缓存优先(Cache first):先查缓存,未命中才走网络。适合带指纹哈希的静态资源——带版本号的 CSS/JS 包、字体、图标。同一个 URL 的内容永不变化,直接读本地既快又安全。

网络优先(Network first):先走网络,失败时回落到缓存。适合会过期的内容——账户页、订单状态,凡是带数字、用户预期"要最新"的页面。在线用户永远拿到新数据,离线用户拿到上一次的版本而不是报错。

先旧后新(Stale while revalidate):立刻返回缓存副本,同时后台请求新版本并更新缓存供下次使用。适合"要秒开、但不必绝对最新"的内容——文章正文、头像、商品列表。用户看到的是差一个版本的内容,多数场景下这个取舍是对的。

只走网络(Network only):完全不缓存。适合一切会改变状态或敏感的请求——支付、鉴权、一次性令牌。有些请求在离线时就应该直接失败,而不是基于过期状态"假装成功"。

多数站点可用的默认组合:/assets/* 缓存优先,HTML 导航请求网络优先,图片先旧后新,/api/checkout 与鉴权接口只走网络。

PWA 离线支持做不到的事

这部分往往在项目中期才被发现,值得提前说清楚。

没缓存过的内容拿不出来。 离线支持回放的是设备上已有的东西。用户从没访问过、你也没预缓存的页面,离线时就是没有。不存在"整站自动镜像"这种能力。

服务端才能完成的事离线做不了。 支付、库存校验、登录,凡是需要服务器响应的动作都无法在离线状态下完成。诚实的做法是把用户意图先排队存进 IndexedDB,等网络恢复后重放——Background Sync API 就是干这个的,但它的浏览器支持并不齐全,一定要留一条手动重试的路径。

存储不保证一直在。 浏览器在磁盘吃紧时会清理缓存数据,而且这个清理你否决不了。navigator.storage.persist() 可以申请持久化存储,浏览器有权拒绝。永远不要把 Cache Storage 或 IndexedDB 当作用户数据的唯一副本。

iOS 表现不一样。 Safari 支持 Service Worker,但存储配额更紧,长期不打开的站点数据可能在约七天不活跃后被清除。只在 Android 上验证过的离线体验,不代表 iOS 用户看到的样子。

首次访问永远不是离线的。 Service Worker 必须先被下载、安装、激活才能起作用,而这需要网络。离线支持保护的是第二次及以后的访问。

上线前怎么测离线

离线相关的问题在常规测试里浮不出来,因为常规测试环境有 Wi-Fi。要专门测:

  1. DevTools → Application → Service Workers → 勾 Offline,然后刷新。这模拟"页面开着但网络已死"。
  2. 直接看 Cache Storage 里到底存了什么。 DevTools → Application → Cache Storage 会列出真实内容。文件不在里面,离线时就取不到,跟你在 install 里"打算缓存什么"无关。
  3. 测更新路径,不只是安装路径。 发一版改动,刷新两次,确认新版本接管、旧缓存被删。用户在第 3 版发布后刷新却仍拿到第 1 版,是最经典的翻车方式。
  4. 关设备的网络开关,别只用 DevTools。 真实飞行模式的行为和模拟离线有差别,iOS 上尤其明显。
  5. 测"离线冷启动"。 关掉所有标签页,断网,从桌面图标启动。这是用户真实会遇到的场景,也最容易暴露某个外壳文件漏缓存。

一条实用的验收线:关掉网络、关掉所有标签页,从桌面图标启动后应当看到你自己的界面和明确的"当前离线"提示,而不是浏览器的错误页。

离线能力放在分发场景里看

很多团队一开始关注 PWA,正是冲着离线能力去的——尤其是在应用商店之外做分发的团队,Web 应用需要"像装好的 App"到可信的程度。在这个语境里,离线支持只是其中一环:安装提示、推送通知、弱网下的表现,共同决定用户把它读成一个"应用"还是一个"网页"。

如果你在更大范围地权衡这条路线,PWA vs APK 讲了两种分发方式在实践中的差异,ROIBest 则在做 Android PWA 分发的交付侧。

常见问题

离线支持需要 manifest 文件吗? 不需要。离线行为完全由 Service Worker 提供。Web App Manifest 管的是可安装性——名称、图标、显示模式。两者是独立能力,只是通常一起上线。

能缓存多少? 取决于浏览器和可用磁盘。Chrome 通常允许单个源占用较大比例的空闲磁盘;Safari 明显更紧。按几十 MB 量级设计,不要按 GB 设计,并且要有选择地缓存。

缓存会不会影响统计数据? 会。走缓存的页面浏览可能根本到不了服务器。统计事件应当从页面侧发送,而不是从服务器日志反推;如果数据准确性重要,离线期间要把事件排队。

能不能不重写现有站点就让它支持离线? 部分可以。加一个"网络优先 + 离线兜底页"的 Service Worker 是一处收敛的改动,能拿到大部分体感收效。但完整的离线功能——断网状态下创建和编辑数据——通常确实需要架构层面的改造。

ROIBest

用 ROIBest 打造下一阶段增长引擎

联系我们,获取稳定、高转化的安卓 PWA 解决方案。