渐进式 Web 应用(PWA)实现指南:将网站变成可安装的 App

不少站长都遇到过这种情况:用户第一次被一篇好文章吸引进来,第二次想回来看更新,却要先翻浏览器历史记录、重新输入网址。PWA(Progressive Web App)要解决的就是这件事——让一个普通网页能被"安装"到桌面或手机主屏幕,拥有独立图标、独立窗口,断网时还能继续浏览已缓存的内容,而实现它只需要一个 manifest 文件加一个 Service Worker。

本文以一个内容站为例,带你走完从配置到上线的全过程。全套代码加起来不到两百行,却能让回访体验和留存率有明显改善。

三个核心组件,缺一不可

PWA 被称为"渐进式",是因为它建立在 Web 既有能力之上,每个组件都是渐进增强,互不依赖:

  1. manifest.json —— 描述应用名称、图标、启动地址、主题色和显示模式,决定"安装"后的样子;
  2. Service Worker —— 独立于页面运行的 JavaScript,负责离线缓存、拦截网络请求、推送通知和后台同步;
  3. HTTPS —— 硬性前提。Service Worker 只能在安全上下文中注册,所以第一步永远是先把 HTTPS 配好,证书类型的选择可以参考 这篇说明

这里有个常被忽略的点:只有 manifest、Service Worker、HTTPS 三者齐全,Chrome 和 Edge 才会在地址栏弹出完整的"安装应用"提示;只配 manifest,用户还能通过菜单手动添加到主屏幕,但体验会差一截。

第一步:配置 manifest.json

manifest 通常放在站点根目录,以 16IDC 为例:

{
  "name": "16IDC",
  "short_name": "16IDC",
  "description": "Cloud service comparison and website building guide",
  "start_url": "/",
  "scope": "/",
  "display": "standalone",
  "background_color": "#ffffff",
  "theme_color": "#4F46E5",
  "lang": "zh-CN",
  "icons": [
    {
      "src": "/icons/icon-192x192.png",
      "sizes": "192x192",
      "type": "image/png",
      "purpose": "any maskable"
    },
    {
      "src": "/icons/icon-512x512.png",
      "sizes": "512x512",
      "type": "image/png",
      "purpose": "any maskable"
    }
  ]
}

几个字段值得留意:scope 限定 Service Worker 可控制的范围,不写默认是 manifest 所在目录;display: standalone 让应用以独立窗口运行、不带浏览器地址栏;purpose: "any maskable" 对应 Android 的自适应图标,图标四周要预留安全边距,否则会被裁掉一圈。start_url 建议写成站内相对路径,避免部分浏览器安装后打开错误页面。

然后在 <head> 中引用 manifest,并补上 iOS 专属的 apple-touch-icon:

<link rel="manifest" href="/manifest.json">
<meta name="theme-color" content="#4F46E5">
<link rel="apple-touch-icon" href="/icons/icon-192x192.png">

不写最后一行的话,iPhone 上"添加到主屏幕"只会生成一个页面截图缩略图,观感很差。

第二步:注册 Service Worker

在页面加载完成后注册 sw.js

// main.js
if ('serviceWorker' in navigator) {
    window.addEventListener('load', async () => {
        try {
            const registration = await navigator.serviceWorker.register('/sw.js');
            console.log('ServiceWorker registered:', registration.scope);
        } catch (error) {
            console.log('ServiceWorker registration failed:', error);
        }
    });
}

建议把注册逻辑放在 load 事件里,避免和页面首屏渲染抢资源。关于前端工程化与框架选型,可参考 前端框架对比

第三步:Service Worker 缓存策略

缓存策略决定"先读缓存还是先走网络",没有放之四海皆准的方案。下面是四种主流策略的对比:

策略 请求流程 适用场景 优点 缺点
Cache First 先查缓存,未命中再走网络并回填 版本化的 CSS、图片等静态资源 秒开、省流量 更新有延迟
Network First 先走网络,失败或超时回退缓存 文章详情、新闻列表 内容新鲜 弱网下稍慢
Stale While Revalidate 先返回缓存,后台静默更新 侧栏、配置类数据 即时显示、最终一致 逻辑稍复杂
Network Only 只走网络 支付、表单提交 数据绝不脏 无离线能力

一个完整的 sw.js 骨架(包含安装、激活、请求拦截):

// sw.js
const CACHE_NAME = 'v1';
const STATIC_ASSETS = [
    '/',
    '/styles.css',
    '/app.js',
    '/offline.html'
];

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

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

// 拦截请求:缓存优先,兜底离线页
self.addEventListener('fetch', event => {
    event.respondWith(
        caches.match(event.request)
            .then(cached => cached || fetch(event.request))
            .catch(() => caches.match('/offline.html'))
    );
});

离线页面与更新机制

offline.html 放进预缓存列表后,用户断网访问未缓存的页面时,会被引导到离线页而不是看到浏览器自带的错误页。离线页通常放一个 Logo、一句"网络开小差了"的提示和一个"重试"按钮:

<!DOCTYPE html>
<html>
<head>
    <title>您处于离线状态</title>
    <style>
        body { text-align: center; padding: 50px; font-family: system-ui; }
    </style>
</head>
<body>
    <h1>暂无网络连接</h1>
    <p>请检查您的网络连接后重试</p>
</body>
</html>

另一个常见坑是"更新不生效":Service Worker 安装时会 skipWaiting,但已经打开的旧页面要等下一次加载才会切到新版本。需要强制刷新的话,可以在页面监听 controllerchange 事件,检测到新版本后提示用户刷新。

推送通知与工具检测

推送通知是 PWA 里最依赖第三方服务的部分:需要申请 VAPID 密钥,把订阅信息交给后端,再由后端通过 Web Push 协议下发,与 FCM、APNs 等平台推送深度绑定。如果只是想实现离线可用,这一项完全可以后置。上线前建议用工具做一次体检:

  • Lighthouse —— Chrome DevTools 内置的 PWA 审计
  • PWABuilder —— Microsoft 的 PWA 打包工具,可生成 Windows/macOS 安装包
  • PWA Checker —— 在线 PWA 合规性检测

常见问题

  • 一定要 HTTPS 吗? 是。Service Worker 只能在 HTTPS 或 localhost 下注册,这是浏览器硬性要求。
  • 图标要准备多大? 至少 192x192 和 512x512 两个尺寸,iOS 再配一个 180x180 的 apple-touch-icon。
  • 会影响 SEO 吗? 不会。PWA 本质仍是普通网页,缓存得当反而能加快加载、提升体验分。
  • 能上架应用商店吗? 可以。通过 PWABuilder 打包成桌面安装包,Android 也能以 Trusted Web Activity 上架 Google Play。

16IDC 观察

实现成本低(一个 manifest 加一个 Service Worker,一两天就能完成)、收益直观(安装率、回访率、弱网体验都明显提升),PWA 是内容站和工具站性价比最高的体验优化之一。想进一步压榨性能,可以配合 网站性能优化指南 一起做。

参考:Mozilla Web 文档 —— https://developer.mozilla.org/zh-CN/docs/Web/Progressive_web_apps ;Google Web Dev —— https://web.dev/learn/pwa