#React#TypeScript

PWA 渐进式 Web 应用

PWA 让 Web 应用拥有原生 app 的能力——离线、安装到桌面、Push 推送、后台同步。本文讲清 PWA 的三大核心(Service Worker/Manifest/Web Push)、实现步骤、以及 2026 年的平台支持现状(iOS 终于支持 Push 了)。

本文要点

  • 主题聚焦:React、TypeScript
  • 适合人群:独立开发者、全栈工程师、AI 应用开发者
  • 核心价值:快速理解背景、实践路径、常见误区和工程建议
作者 @admin·发布于 2026年3月27日·更新于 2026年7月23日

"PWA 还有人用吗?"——这是我近两年最常被问的问题之一。自从 2018 年苹果给 iOS 的 PWA 砍了一刀又一刀,这个概念确实凉过一阵。但到了 2026 年,情况变了:iOS 16.4 终于补上了 Web Push 这块最大的短板,而 Chrome/Edge 那边的 PWA 能力早就齐活。

所以值得重新把 PWA 捋一遍。这篇文章不讲"什么是 PWA"这种百科内容,讲的是三大核心怎么协作、怎么落地、以及 2026 年到底什么场景该选 PWA。

一、PWA 到底解决什么问题

一句话:让 Web 应用获得原生 app 的体验,却不用上架 App Store。

具体是四种能力:

  1. 离线可用——断网也能打开,内容从缓存读出来。
  2. 安装到桌面——用户能把 Web 应用"装"成桌面图标,双击直接全屏启动,没有浏览器地址栏那一圈 chrome。
  3. 接收 Push 推送——服务端能给用户发通知,哪怕应用没开。
  4. 后台同步——网络恢复后,自动把离线时的操作(发帖、提交表单)同步回服务端。

这四件事,过去是原生 app 的专利。PWA 的赌注是:对绝大多数内容类、工具类应用,用户根本不想为了一张壁纸、一个记事本去下载一个 80MB 的 app。Web 加上这四种能力,体验已经够用了。

注意"够用"这两个字——PWA 不取代原生,它取代的是"本来就不该做成 app 的东西"。这点我后面会展开。

二、三大核心:Service Worker / Manifest / Web Push

PWA 不是单一技术,是三块拼起来的。

1. Service Worker(简称 SW) 是大脑。它是一段跑在浏览器后台的 JS,独立于网页,能拦截网络请求。所有离线、缓存、Push 收件、后台同步都靠它。SW 有几个反直觉的特性:它不能直接碰 DOM;它有自己的生命周期(install → activate);浏览器会在不需要时杀掉它、需要时再唤醒——所以别在 SW 里存运行时状态,所有状态都得走 Cache Storage 或 IndexedDB。

2. Web App Manifest 是身份。一个 JSON 文件,告诉浏览器这个站"叫什么名字、什么图标、主题色、启动要不要全屏"。没它,浏览器只当你是普通网页;有了它 + 一个注册上的 SW,地址栏右边就会冒出"安装"按钮。

3. Web Push API 是喉咙。基于 VAPID 协议,服务端通过推送服务(Chrome 走 FCM,Firefox/Edge 走自家的,Safari 走 APNs)把消息推到用户设备,SW 在后台收到并显示通知。用户不需要打开应用就能收到推送——这是 PWA 最接近原生 app 的体验。

三、实现步骤:从零搭一个能装的 PWA

四步走。

第一步:写 manifest.json。最关键的几个字段:

json
{
  "name": "CodeNodeX",
  "short_name": "CodeNodeX",
  "description": "AI 开发者社区",
  "start_url": "/",
  "display": "standalone",
  "background_color": "#0a0a0a",
  "theme_color": "#0a0a0a",
  "icons": [
    { "src": "/icon-192.png", "sizes": "192x192", "type": "image/png" },
    { "src": "/icon-512.png", "sizes": "512x512", "type": "image/png", "purpose": "any maskable" }
  ]
}

display: "standalone" 是关键——装上之后启动就没有浏览器 chrome 了,看起来就是个独立 app。purpose: "maskable" 让 Android 自适应图标形状,不做的话图标在 Android 上会被强行切圆,丑。

HTML 里挂上:<link rel="manifest" href="/manifest.json">。

第二步:注册 Service Worker。在入口文件里(别在 SW 文件里):

ts
if ('serviceWorker' in navigator) {
  window.addEventListener('load', () => {
    navigator.serviceWorker.register('/sw.js').catch(console.error);
  });
}

第三步:写 SW,选缓存策略。这是真正决定离线体验的地方。最常用的是两种:

  • Cache First(缓存优先):静态资源(JS/CSS/字体/图标)用这个。先查缓存,命中就直接返回,没命中再走网络并存起来。适合不变的资源。
  • Network First(网络优先):动态内容(API 响应、文章正文)用这个。先请求网络,成功就用并更新缓存;失败再 fallback 到缓存。保证用户拿到最新数据,同时断网能用。
  • Stale While Revalidate:折中——立即返回缓存(快),同时后台去拉新的更新缓存(下次就用上)。适合非关键又希望保持新鲜的资源。

一个最小可用的 SW:

js
const CACHE = 'codenodex-v1';
const ASSETS = ['/', '/styles.css', '/app.js', '/icon-192.png'];

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

self.addEventListener('fetch', (e) => {
  const { request } = e;
  // 动态接口走 network-first
  if (request.url.includes('/api/')) {
    e.respondWith(
      fetch(request).then((res) => {
        const copy = res.clone();
        caches.open(CACHE).then((c) => c.put(request, copy));
        return res;
      }).catch(() => caches.match(request))
    );
    return;
  }
  // 其余走 cache-first
  e.respondWith(
    caches.match(request).then((hit) => hit || fetch(request).then((res) => {
      const copy = res.clone();
      caches.open(CACHE).then((c) => c.put(request, copy));
      return res;
    }))
  );
});

第四步:HTTPS。SW 必须在 HTTPS 下注册(localhost 例外)。没 HTTPS 一切免谈——这也是为啥有些内部工具上不了 PWA,先解决证书。

四、离线策略:App Shell + 动态数据

真正好用的离线不是"全站打包",是分层。

App Shell 模型:把应用的"骨架"——HTML 外壳、导航、样式、核心 JS——预缓存下来。用户离线打开时,Shell 秒开,内容区域显示"离线"占位或上次缓存的数据。这套模式特别适合 SPA / 内容站,因为它把"UI 框架"和"数据"解耦了,前者必须离线可用,后者尽力而为。

动态数据走两条路:一是上面说的 stale-while-revalidate,给用户"看起来有数据"的体验;二是 Background Sync(后台同步)——用户离线时提交了评论/表单,浏览器记住这件事,等网络恢复后 SW 自动重试。这是体验上最接近原生 app 的一环,用户在网络抖动时提交不会丢。

五、安装体验:Add to Home Screen

Manifest 配好 + SW 注册上,Chrome/Edge 就会触发 beforeinstallprompt 事件,你可以自定义"安装"按钮(而不是等浏览器自发的弹窗,那个转化率很低):

ts
let deferredPrompt: BeforeInstallPromptEvent | null = null;
window.addEventListener('beforeinstallprompt', (e) => {
  e.preventDefault();
  deferredPrompt = e;
  showInstallButton(); // 你自己的 UI
});

installButton.onclick = async () => {
  if (!deferredPrompt) return;
  deferredPrompt.prompt();
  await deferredPrompt.userChoice;
  deferredPrompt = null;
};

装上之后,桌面/主屏幕多一个图标,启动直接全屏 standalone,任务栏、Dock 里和原生 app 没区别。这就是 PWA 在"安装体验"上的终极目标。

六、2026 年平台支持现状:终于补齐了

这是这篇文章我最想强调的部分,因为它直接决定你敢不敢上 PWA。

  • Chrome / Edge(桌面):全功能。安装、SW、Push、后台同步、文件系统、定时同步、甚至协议处理器,基本能给的都给了。
  • Android Chrome:同样完整,Push 走 FCM,安装体验最成熟。Android 是 PWA 体验最好的平台,没有之一。
  • iOS / iPadOS 16.4+:历史性突破——终于支持 Web Push。装到主屏幕的 PWA 能像原生 app 一样收到推送通知(走 APNs,需要用户从主屏幕图标启动一次 PWA 才能授权)。在这之前,iOS PWA 最大的短板就是"装上之后收不到推送",基本废了一半。现在补上了。
  • 桌面 Safari(macOS):支持 manifest 和基本 SW,但 macOS Safari 至今不支持 Web Push(只有 iOS 16.4+ 支持)。所以面向 Mac 用户的推送需求,PWA 还兜不住。

一句话总结 2026 现状:移动端 PWA 已经是可用产品,不是玩具。iOS 16.4 是分水岭,之前能做但残缺,现在终于闭环了。

七、PWA vs 原生 vs 跨平台:别选错赛道

三种方案怎么选,我只看一条:你这个应用,到底需不需要原生能力。

  • PWA 适合:内容消费(新闻、博客、社区)、工具类(记事本、转换器、计算器)、轻交互的后台管理。这类应用不需要蓝牙、不需要传感器、不需要后台常驻,用户也不愿为它专门下载 app。
  • 原生适合:重度图形(游戏、视频剪辑)、系统级集成(健康、相机、AR)、强后台(即时通讯 IM 必须用原生 Push + 长连接)、对启动性能极致敏感的(秒开相机)。
  • 跨平台(RN/Flutter):折中——要上 App Store、又想一套代码两端跑。代价是包体积、首屏性能、原生桥接的复杂度。

我的观点很直白:PWA 不是"取代原生"的路径,是"Web 做更多事"的路径。 它把过去必须做 app 的场景拉回到 Web——但真正吃硬件、吃系统、吃后台的场景,原生依然是不可替代的。别相信"PWA 杀死原生"这种话,也别相信"PWA 没用"——它有用,但用对地方。

八、Next.js 项目:next-pwa 是最快的捷径

如果你用 Next.js(这个站就是),别手写上面那一堆。next-pwa 在 App Router 下能自动生成 manifest、自动注册 SW、自动处理缓存策略:

ts
// next.config.ts
import withPWA from 'next-pwa';

export default withPWA({
  dest: 'public',
  register: true,
  skipWaiting: true,
})(nextConfig);

它底层用 Workbox(Google 出品的 SW 工具集),缓存策略(预缓存、运行时缓存、各种 fallback)都是预置好的,你只需要在 runtimeCaching 配置里声明"哪些路径用什么策略"。对绝大多数项目,手写 SW 是在重新发明一个更差的 Workbox——除非有非常定制化的需求,直接用就行。

小结

  • PWA 的价值是"Web 拿到原生 app 的四种能力":离线、安装、Push、后台同步。不是取代原生,是让本不该做 app 的东西留在 Web。
  • 三大核心分工明确:Service Worker 是大脑(拦截请求、缓存、Push 收件),Manifest 是身份(名称/图标/全屏),Web Push 是喉咙(收推送)。
  • 缓存策略三选一:静态资源 cache-first,动态接口 network-first,中性资源 stale-while-revalidate。App Shell + 后台同步是离线体验的标准组合拳。
  • 2026 年平台支持:Chrome/Edge/Android 全面 OK,iOS 16.4+ 终于支持 Web Push——这是 PWA 闭环的关键。但 macOS Safari 仍不支持 Push,这是已知短板。
  • Next.js 项目用 next-pwa,底层 Workbox 已经把脏活干完,别手写。

一句话收尾:PWA 不该是技术选型会议上的辩论题,而该是"这个站本来就该有的体验"。给一个内容站加上离线和桌面图标,用户不会因此夸你,但少装一个 app、少收一个推送权限,体验就悄悄好了一截。

Related

相关推荐