pwa-expert

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Progressive Web App Expert

PWA开发专家

Build installable, offline-capable web apps with Service Workers, smart caching, and native-like experiences.
借助Service Worker、智能缓存和类原生体验,构建可安装、支持离线的Web应用。

When to Use This Skill

适用场景

  • Making a web app installable on mobile/desktop
  • Implementing offline functionality
  • Setting up Service Worker caching strategies
  • Handling install prompts (
    beforeinstallprompt
    )
  • Background sync for offline-first apps
  • Managing PWA update flows
  • Creating web app manifests
  • 让Web应用可在移动设备/桌面端安装
  • 实现离线功能
  • 配置Service Worker缓存策略
  • 处理安装提示(
    beforeinstallprompt
    事件)
  • 为离线优先应用配置后台同步
  • 管理PWA更新流程
  • 创建Web应用清单

When NOT to Use This Skill

不适用场景

  • Native app development → Use React Native, Flutter, or native SDKs
  • General web performance → Use Lighthouse/performance auditing tools
  • Server-side rendering issues → Use Next.js/framework-specific docs
  • Push notifications only → Consider dedicated push notification services
  • Simple static sites → PWA overhead may not be worth it
  • 原生应用开发 → 使用React Native、Flutter或原生SDK
  • 通用Web性能优化 → 使用Lighthouse或性能审计工具
  • 服务端渲染问题 → 使用Next.js或对应框架的文档
  • 仅需推送通知 → 考虑专用的推送通知服务
  • 简单静态网站 → PWA的额外开销得不偿失

Core Concepts

核心概念

What Makes a PWA Installable

PWA可安装的条件

  1. HTTPS (or localhost for dev)
  2. Web App Manifest with required fields
  3. Service Worker with fetch handler
  4. Icons (192×192 and 512×512 minimum)
  1. HTTPS(开发环境可使用localhost)
  2. 包含必填字段的Web应用清单(Web App Manifest)
  3. 带有fetch处理器的Service Worker
  4. 图标(最小尺寸192×192和512×512)

The PWA Stack

PWA技术栈

┌─────────────────────────────────────────┐
│           Your App (React/Next.js)      │
├─────────────────────────────────────────┤
│         Service Worker (sw.js)          │
│  ┌─────────────┐  ┌─────────────────┐   │
│  │   Cache     │  │  Network Fetch  │   │
│  │   Storage   │  │    Handling     │   │
│  └─────────────┘  └─────────────────┘   │
├─────────────────────────────────────────┤
│          manifest.json                  │
│  (App identity, icons, display mode)    │
└─────────────────────────────────────────┘
┌─────────────────────────────────────────┐
│           你的应用(React/Next.js)      │
├─────────────────────────────────────────┤
│         Service Worker (sw.js)          │
│  ┌─────────────┐  ┌─────────────────┐   │
│  │   缓存(Cache) │  │  网络请求处理  │   │
│  │   存储(Storage)│  │               │   │
│  └─────────────┘  └─────────────────┘   │
├─────────────────────────────────────────┤
│          manifest.json                  │
│ (应用标识、图标、显示模式)            │
└─────────────────────────────────────────┘

Web App Manifest

Web应用清单(Web App Manifest)

Complete manifest.json

完整的manifest.json示例

json
{
  "name": "Junkie Buds 4 Life",
  "short_name": "JB4L",
  "description": "Recovery support app",
  "start_url": "/",
  "scope": "/",
  "display": "standalone",
  "orientation": "portrait-primary",
  "background_color": "#1a1410",
  "theme_color": "#1a1410",
  "icons": [
    {
      "src": "/icons/icon-192.png",
      "sizes": "192x192",
      "type": "image/png",
      "purpose": "any"
    },
    {
      "src": "/icons/icon-512.png",
      "sizes": "512x512",
      "type": "image/png",
      "purpose": "any"
    },
    {
      "src": "/icons/icon-maskable-512.png",
      "sizes": "512x512",
      "type": "image/png",
      "purpose": "maskable"
    }
  ],
  "shortcuts": [
    {
      "name": "Find Meetings",
      "short_name": "Meetings",
      "url": "/meetings?source=shortcut",
      "icons": [{ "src": "/icons/meetings-96.png", "sizes": "96x96" }]
    }
  ]
}
json
{
  "name": "Junkie Buds 4 Life",
  "short_name": "JB4L",
  "description": "康复支持应用",
  "start_url": "/",
  "scope": "/",
  "display": "standalone",
  "orientation": "portrait-primary",
  "background_color": "#1a1410",
  "theme_color": "#1a1410",
  "icons": [
    {
      "src": "/icons/icon-192.png",
      "sizes": "192x192",
      "type": "image/png",
      "purpose": "any"
    },
    {
      "src": "/icons/icon-512.png",
      "sizes": "512x512",
      "type": "image/png",
      "purpose": "any"
    },
    {
      "src": "/icons/icon-maskable-512.png",
      "sizes": "512x512",
      "type": "image/png",
      "purpose": "maskable"
    }
  ],
  "shortcuts": [
    {
      "name": "查找会议",
      "short_name": "会议",
      "url": "/meetings?source=shortcut",
      "icons": [{ "src": "/icons/meetings-96.png", "sizes": "96x96" }]
    }
  ]
}

Display Modes

显示模式

ModeDescription
fullscreen
No browser UI, full screen
standalone
App-like, no URL bar (recommended)
minimal-ui
Some browser controls
browser
Normal browser tab
模式描述
fullscreen
无浏览器UI,全屏显示
standalone
类应用样式,无地址栏(推荐)
minimal-ui
保留部分浏览器控件
browser
普通浏览器标签页

Link in HTML

在HTML中引入

html
<head>
  <link rel="manifest" href="/manifest.json" />
  <meta name="theme-color" content="#1a1410" />
  <meta name="apple-mobile-web-app-capable" content="yes" />
  <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
  <link rel="apple-touch-icon" href="/icons/apple-touch-icon.png" />
</head>
html
<head>
  <link rel="manifest" href="/manifest.json" />
  <meta name="theme-color" content="#1a1410" />
  <meta name="apple-mobile-web-app-capable" content="yes" />
  <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
  <link rel="apple-touch-icon" href="/icons/apple-touch-icon.png" />
</head>

Service Worker Basics

Service Worker基础

Registration

注册

typescript
// lib/pwa.ts
export async function registerServiceWorker() {
  if ('serviceWorker' in navigator) {
    try {
      const registration = await navigator.serviceWorker.register('/sw.js', {
        scope: '/',
      });
      return registration;
    } catch (error) {
      console.error('SW registration failed:', error);
    }
  }
}

// Call on app mount
useEffect(() => {
  registerServiceWorker();
}, []);
typescript
// lib/pwa.ts
export async function registerServiceWorker() {
  if ('serviceWorker' in navigator) {
    try {
      const registration = await navigator.serviceWorker.register('/sw.js', {
        scope: '/',
      });
      return registration;
    } catch (error) {
      console.error('SW注册失败:', error);
    }
  }
}

// 在应用挂载时调用
useEffect(() => {
  registerServiceWorker();
}, []);

Basic Service Worker Structure

基础Service Worker结构

javascript
// public/sw.js
const CACHE_NAME = 'myapp-v1';
const STATIC_ASSETS = ['/', '/offline', '/manifest.json'];

// Install: Cache static assets
self.addEventListener('install', (event) => {
  event.waitUntil(
    caches.open(CACHE_NAME).then((cache) => cache.addAll(STATIC_ASSETS))
  );
  self.skipWaiting();
});

// Activate: Clean old caches
self.addEventListener('activate', (event) => {
  event.waitUntil(
    caches.keys().then((keys) =>
      Promise.all(keys.filter((k) => k !== CACHE_NAME).map((k) => caches.delete(k)))
    )
  );
  self.clients.claim();
});

// Fetch: Handle requests (see references for strategies)
self.addEventListener('fetch', (event) => {
  event.respondWith(handleFetch(event.request));
});
See:
references/service-worker-patterns.md
for caching strategy implementations
javascript
// public/sw.js
const CACHE_NAME = 'myapp-v1';
const STATIC_ASSETS = ['/', '/offline', '/manifest.json'];

// 安装:缓存静态资源
self.addEventListener('install', (event) => {
  event.waitUntil(
    caches.open(CACHE_NAME).then((cache) => cache.addAll(STATIC_ASSETS))
  );
  self.skipWaiting();
});

// 激活:清理旧缓存
self.addEventListener('activate', (event) => {
  event.waitUntil(
    caches.keys().then((keys) =>
      Promise.all(keys.filter((k) => k !== CACHE_NAME).map((k) => caches.delete(k)))
    )
  );
  self.clients.claim();
});

// 网络请求:处理请求(缓存策略实现请参考参考文献)
self.addEventListener('fetch', (event) => {
  event.respondWith(handleFetch(event.request));
});
参考:
references/service-worker-patterns.md
中的缓存策略实现

Caching Strategies

缓存策略

StrategyBest ForTradeoff
Cache-FirstStatic assets, fonts, imagesStale until cache updated
Network-FirstAPI data, user contentSlower, needs connectivity
Stale-While-RevalidateBalance freshness/speedBackground updates
Network-OnlyAuth, real-time dataNo offline support
Cache-OnlyVersioned assetsNever updates
See:
references/service-worker-patterns.md
for full implementations
策略适用场景权衡点
Cache-First静态资源、字体、图片缓存未更新前内容可能过时
Network-FirstAPI数据、用户内容速度较慢,需要网络连接
Stale-While-Revalidate平衡新鲜度与速度在后台更新内容
Network-Only认证请求、实时数据无离线支持
Cache-Only版本化资源永不自动更新
参考:
references/service-worker-patterns.md
中的完整实现

Install Prompts

安装提示

Handle the
beforeinstallprompt
event to show a custom install UI:
typescript
// Basic pattern
const [deferredPrompt, setDeferredPrompt] = useState(null);

useEffect(() => {
  window.addEventListener('beforeinstallprompt', (e) => {
    e.preventDefault();
    setDeferredPrompt(e);
  });
}, []);

const handleInstall = async () => {
  if (deferredPrompt) {
    deferredPrompt.prompt();
    const { outcome } = await deferredPrompt.userChoice;
    // outcome: 'accepted' or 'dismissed'
  }
};
See:
references/install-prompt.md
for full
usePWAInstall
hook and component
处理
beforeinstallprompt
事件以显示自定义安装界面:
typescript
// 基础实现
const [deferredPrompt, setDeferredPrompt] = useState(null);

useEffect(() => {
  window.addEventListener('beforeinstallprompt', (e) => {
    e.preventDefault();
    setDeferredPrompt(e);
  });
}, []);

const handleInstall = async () => {
  if (deferredPrompt) {
    deferredPrompt.prompt();
    const { outcome } = await deferredPrompt.userChoice;
    // outcome值为'accepted'或'dismissed'
  }
};
参考:
references/install-prompt.md
中的完整
usePWAInstall
钩子和组件实现

Offline Experience

离线体验

Key patterns:
  • Offline page fallback for navigation failures
  • useOnlineStatus
    hook to detect connectivity
  • Offline banner to inform users
See:
references/offline-handling.md
for implementations
关键实现模式:
  • 导航失败时的离线页面回退
  • useOnlineStatus
    钩子检测网络连接状态
  • 离线提示横幅告知用户
参考:
references/offline-handling.md
中的实现方案

Background Sync

后台同步

Queue actions while offline, execute when connectivity returns:
javascript
// In Service Worker
self.addEventListener('sync', (event) => {
  if (event.tag === 'sync-data') {
    event.waitUntil(syncPendingData());
  }
});

// In App - trigger sync
const registration = await navigator.serviceWorker.ready;
await registration.sync.register('sync-data');
See:
references/background-sync.md
for full IndexedDB integration
在离线时排队操作,恢复网络连接后执行:
javascript
// 在Service Worker中
self.addEventListener('sync', (event) => {
  if (event.tag === 'sync-data') {
    event.waitUntil(syncPendingData());
  }
});

// 在应用中 - 触发同步
const registration = await navigator.serviceWorker.ready;
await registration.sync.register('sync-data');
参考:
references/background-sync.md
中的IndexedDB集成完整实现

Update Flow

更新流程

Notify users when a new version is available:
typescript
// Basic pattern
registration.addEventListener('updatefound', () => {
  const newWorker = registration.installing;
  newWorker?.addEventListener('statechange', () => {
    if (newWorker.state === 'installed' && navigator.serviceWorker.controller) {
      // New version available - show update prompt
    }
  });
});
See:
references/update-flow.md
for
usePWAUpdate
hook and update strategies
当有新版本可用时通知用户:
typescript
// 基础实现
registration.addEventListener('updatefound', () => {
  const newWorker = registration.installing;
  newWorker?.addEventListener('statechange', () => {
    if (newWorker.state === 'installed' && navigator.serviceWorker.controller) {
      // 新版本可用 - 显示更新提示
    }
  });
});
参考:
references/update-flow.md
中的
usePWAUpdate
钩子和更新策略

Next.js Integration

Next.js集成

Options for Next.js PWA:
  1. next-pwa - Works with standard Next.js server
  2. Custom SW - Required for
    output: 'export'
    (static sites)
  3. Workbox CLI - Generate SW after build
See:
references/nextjs-integration.md
for detailed configurations
Next.js中实现PWA的选项:
  1. next-pwa - 适用于标准Next.js服务
  2. 自定义SW - 当使用
    output: 'export'
    (静态站点)时必需
  3. Workbox CLI - 构建后生成Service Worker
参考:
references/nextjs-integration.md
中的详细配置

Quick Reference

快速参考

TaskSolution
Check if installed
window.matchMedia('(display-mode: standalone)').matches
Force SW update
registration.update()
Clear all caches
caches.keys().then(keys => keys.forEach(k => caches.delete(k)))
Check online
navigator.onLine
Get SW registration
navigator.serviceWorker.ready
Skip waiting
self.skipWaiting()
in SW
Take control
self.clients.claim()
in SW
任务解决方案
检查应用是否已安装
window.matchMedia('(display-mode: standalone)').matches
强制Service Worker更新
registration.update()
清除所有缓存
caches.keys().then(keys => keys.forEach(k => caches.delete(k)))
检查网络状态
navigator.onLine
获取Service Worker注册实例
navigator.serviceWorker.ready
跳过等待直接激活在Service Worker中调用
self.skipWaiting()
立即控制所有客户端在Service Worker中调用
self.clients.claim()

Testing PWA

PWA测试

Chrome DevTools

Chrome开发者工具

  1. Application tab → Manifest, Service Workers, Cache Storage
  2. Lighthouse → PWA audit
  3. Network → Offline checkbox to simulate
  1. Application标签 → 查看Manifest、Service Workers、Cache Storage
  2. Lighthouse → 执行PWA审计
  3. Network标签 → 勾选Offline模拟离线环境

Debug Checklist

调试检查清单

  • Manifest loads (Application → Manifest)
  • SW registered (Application → Service Workers)
  • Cache populated (Application → Cache Storage)
  • Install prompt fires (Console for beforeinstallprompt)
  • Offline page works (Network → Offline)
  • Update flow works (trigger update, verify prompt)
  • Manifest加载正常(Application → Manifest)
  • Service Worker已注册(Application → Service Workers)
  • 缓存已填充(Application → Cache Storage)
  • 安装提示触发正常(控制台查看beforeinstallprompt事件)
  • 离线页面可正常访问(Network → Offline)
  • 更新流程正常(触发更新,验证提示)

References

参考文献

Detailed implementations in
/references/
:
  • service-worker-patterns.md
    - Caching strategy implementations
  • install-prompt.md
    -
    usePWAInstall
    hook and install component
  • offline-handling.md
    - Offline page, status hooks, banners
  • background-sync.md
    - Background sync with IndexedDB
  • update-flow.md
    - Update detection and user prompts
  • nextjs-integration.md
    - Next.js PWA configuration options
详细实现方案位于
/references/
目录:
  • service-worker-patterns.md
    - 缓存策略实现
  • install-prompt.md
    -
    usePWAInstall
    钩子和安装组件
  • offline-handling.md
    - 离线页面、状态钩子、提示横幅
  • background-sync.md
    - 结合IndexedDB的后台同步
  • update-flow.md
    - 更新检测和用户提示
  • nextjs-integration.md
    - Next.js PWA配置选项