📖 核心概念
- PWA核心:Manifest + Service Worker
- Service Worker生命周期
- 离线缓存策略
- 推送通知
- 安装到桌面
- Workbox工具链
💻 代码实现
PWA配置与Service Worker
✅ typescript
// next.config.js - PWA配置
const withPWA = require('next-pwa')({
dest: 'public',
register: true,
skipWaiting: true,
disable: process.env.NODE_ENV === 'development',
});
module.exports = withPWA({
// ...next配置
});
// public/manifest.json
const manifest = {
name: '我的应用',
short_name: ' MyApp',
description: '一个现代化的PWA应用',
start_url: '/',
display: 'standalone',
background_color: '#0f172a',
theme_color: '#06b6d4',
orientation: 'portrait-primary',
icons: [
{ src: '/icons/icon-192.png', sizes: '192x192', type: 'image/png' },
{ src: '/icons/icon-512.png', sizes: '512x512', type: 'image/png' },
{ src: '/icons/icon-512.png', sizes: '512x512', type: 'image/png', purpose: 'maskable' },
],
categories: ['productivity', 'utilities'],
screenshots: [
{ src: '/screenshots/home.png', sizes: '1280x720', type: 'image/png', form_factor: 'wide' },
],
};
// Service Worker缓存策略
// sw.ts (使用Workbox)
import { registerRoute } from 'workbox-routing';
import {
CacheFirst, NetworkFirst, StaleWhileRevalidate,
NetworkOnly,
} from 'workbox-strategies';
import { ExpirationPlugin } from 'workbox-expiration';
import { precacheAndRoute } from 'workbox-precaching';
// 预缓存(构建时生成)
precacheAndRoute(self.__WB_MANIFEST);
// 静态资源 - 缓存优先
registerRoute(
({ request }) => request.destination === 'image',
new CacheFirst({
cacheName: 'images',
plugins: [
new ExpirationPlugin({ maxEntries: 100, maxAgeSeconds: 30 * 24 * 60 * 60 }),
],
})
);
// API请求 - 网络优先
registerRoute(
({ url }) => url.pathname.startsWith('/api/'),
new NetworkFirst({
cacheName: 'api-cache',
networkTimeoutSeconds: 3,
plugins: [
new ExpirationPlugin({ maxEntries: 50, maxAgeSeconds: 5 * 60 }),
],
})
);
// 第三方资源 - 过期重验证
registerRoute(
({ url }) => url.origin === 'https://cdn.example.com',
new StaleWhileRevalidate({ cacheName: 'cdn-cache' })
);
安装与推送通知
✅ tsx
// PWA安装提示
import { useState, useEffect } from 'react';
function usePWAInstall() {
const [deferredPrompt, setDeferredPrompt] = useState<any>(null);
const [isInstallable, setIsInstallable] = useState(false);
useEffect(() => {
const handler = (e: Event) => {
e.preventDefault();
setDeferredPrompt(e);
setIsInstallable(true);
};
window.addEventListener('beforeinstallprompt', handler);
return () => window.removeEventListener('beforeinstallprompt', handler);
}, []);
const install = async () => {
if (!deferredPrompt) return;
deferredPrompt.prompt();
const { outcome } = await deferredPrompt.userChoice;
console.log('安装结果:', outcome);
setDeferredPrompt(null);
setIsInstallable(false);
};
return { isInstallable, install };
}
// 推送通知
async function requestNotificationPermission() {
if (!('Notification' in window)) return false;
const permission = await Notification.requestPermission();
return permission === 'granted';
}
async function subscribeToPush(vapidKey: string) {
const registration = await navigator.serviceWorker.ready;
const subscription = await registration.pushManager.subscribe({
userVisibleOnly: true,
applicationServerKey: vapidKey,
});
// 将subscription发送到服务器
await fetch('/api/push/subscribe', {
method: 'POST',
body: JSON.stringify(subscription),
});
return subscription;
}
// 离线状态检测
function useOnlineStatus() {
const [isOnline, setIsOnline] = useState(navigator.onLine);
useEffect(() => {
const online = () => setIsOnline(true);
const offline = () => setIsOnline(false);
window.addEventListener('online', online);
window.addEventListener('offline', offline);
return () => {
window.removeEventListener('online', online);
window.removeEventListener('offline', offline);
};
}, []);
return isOnline;
}
function OfflineIndicator() {
const isOnline = useOnlineStatus();
if (isOnline) return null;
return (
<div className="offline-banner" role="alert">
📴 当前处于离线模式,部分功能可能不可用
</div>
);
}
🎯 练习任务
- 1 将一个Next.js应用转换为PWA
- 2 实现离线缓存策略,确保离线可用
- 3 添加推送通知功能
- 4 实现安装到桌面的提示流程
🏆 成就解锁
PWA开发者 — 掌握PWA开发技术,让Web应用拥有原生体验