PWA 离线支持

通过 @vite-pwa/nuxt 实现 Progressive Web App,支持 Service Worker 自动生成、离线缓存和添加到桌面提示。

启用 PWA

// nuxt.config.ts
swiftConfig: {
  pwa: true,
}

Service Worker 自动生成

启用 PWA 后,构建时自动生成 Service Worker(Workbox),各类资源缓存策略如下:

  • JS / CSS / 字体(woff2):构建期 precache(skipWaiting + clientsClaim,部署新版后下次请求即生效)
  • 图片:Stale-While-Revalidate(先回缓存、后台拉新,30 天 / 最多 60 条)
  • /api 请求:NetworkFirst(网络优先,10s 超时回退缓存)
  • 页面 HTML:不经 Service Worker(navigateFallback: null,始终走网络 / Nitro)

Web App Manifest

PWA 的安装体验("添加到桌面")依赖 manifest.json,框架自动生成。通过 public/icons/ 目录放置图标文件:

  • icon-192.png — Android 主屏图标
  • icon-512.png — 高分辨率图标 + 启动画面
  • apple-touch-icon.png — iOS 主屏图标

manifest 的 head 注入由 framework 负责

生成 manifest 文件与把它 link 进 head 是两件事。@vite-pwa/nuxt 只把 VitePwaManifest 注册成一个可自动导入的组件,需要 app 自己挂载才会输出 <link rel="manifest"> —— 挂漏了, manifest 文件照样生成、Service Worker 照样注册,但浏览器从来读不到它。

framework 直接在构建期把这行注入 app.head(fw#355),applier 无需挂 <VitePwaManifest />:

<link rel="manifest" href="/manifest.webmanifest" />
<meta name="apple-mobile-web-app-title" content="{pwa.shortName}" />
  • href 跟随 app.baseURL(子路径部署自动拼对),也尊重顶层 vite-pwa.filename 覆盖
  • applier 若自己在 nuxt.config 写过 link[rel=manifest] 或该条 meta,framework 不重复注入
  • dev 期默认不注入 —— @vite-pwa 在 dev 下不产 manifest 文件,注入只会得到 404; 本地要调 PWA 时设 SWIFT_WEB_PWA_DEV=true,manifest 与该 link 一起生效

iOS「添加到主屏」的名字从哪来

iOS 的主屏名称有两个来源,优先级与版本相关,framework 两条都喂:

来源谁读它
apple-mobile-web-app-title meta所有 iOS 版本;已装的 legacy web clip 也只认它
manifest 的 short_name支持 manifest 的 iOS(需要上面那条 link 存在)

两者的值同源于 pwa.manifest.short_name,不会互相打架。没有这条 meta 且 manifest 没被 link 时,iOS 会退回整页 <title> —— 这正是 fw#353 的症状(预填名变成「副标题 | 站点全名」)。

framework 有意不注入 apple-mobile-web-app-capable(已被 manifest display 取代、MDN 标 deprecated)与 apple-mobile-web-app-status-bar-style(属视觉决策——black-translucent 会让 内容顶到状态栏下,应由 applier 自己定)。

需要这两条、或想让项目自己接管主屏名时,写在 nuxt.config.ts 的 app.head.meta 即可 —— framework 见到同名 meta 就不再注入自己那条(幂等):

app: {
  head: {
    meta: [
      // 自己接管主屏名(不写则由 framework 用 pwa.manifest.short_name 注入)
      { name: 'apple-mobile-web-app-title', content: '浪花湾' },
      // ⚠️ black-translucent 会让内容顶到状态栏下,需自行留安全区:
      //    给顶部容器加 padding-top: env(safe-area-inset-top)
      { name: 'apple-mobile-web-app-status-bar-style', content: 'black-translucent' },
    ],
  },
},

改名字优先改 swift.config.json 的 pwa.manifest.short_name——它与 manifest short_name 同源,iOS / Android 一起变。

manifest 字段来源一览

manifest 字段来源说明
nameswift.config.json 的 project.title(经 nuxt.config 接线)应用全名。与站点标题同源,改标题一处即可
short_namenuxt.config.ts 顶层 pwa.manifest.short_name主屏图标下方的名字,也是 iOS 添加主屏的预填值。默认同 title,超过约 12 字要自己改短
descriptionswift.config.json 的 project.description(经 nuxt.config 接线)与 SEO 描述同源
theme_colornuxt.config.ts 顶层 pwa.manifest.theme_colorAndroid 任务卡片 / 系统 chrome 配色。UI 稿出来后改
background_colornuxt.config.ts 顶层 pwa.manifest.background_color(默认 #ffffff)冷启动铺满的那一屏底色,深色站必配,见下
lang自动取 framework.i18n.defaultLocale不另开配置面
display / start_url / iconsframework 固定见上文

为什么分两处:「开不开 PWA」在 swift.config.json(sw config set framework.pwa true / false, CLI 管;不再联动装卸 @vite-pwa/nuxt,该模块由 framework 统一携带);「叫什么、什么颜色」在 nuxt.config.ts —— 建项目时你只有一个站名, 描述没想好、UI 稿没出,颜色问不出来;而 JSON 里没注释、看不见的字段事后没人回头补。 starter 模板已把这些字段逐条接好线并带注释,改的时候看得见。

🔴 深色站必配 backgroundColor

PWA 从主屏图标冷启动时,系统先按 background_color 铺满一屏、再画首帧。默认值是 #ffffff, 深色站不配就会先闪一下白。

这个白闪只在真机冷启动的一瞬出现 —— PC 与开发期几乎撞不到,属于「写错了自己看不见」那一类, 所以 framework 给了配置口子而不是让业务自己去覆盖 manifest。取站点主背景色,改 nuxt.config.ts:

pwa: {
  manifest: {
    // …
    background_color: '#0a1213', // 深色站主背景色
  },
},

其余 manifest 字段:顶层 pwa 逃生舱

上表之外的字段(categories / shortcuts / screenshots / 换 icons 等)走 nuxt.config.ts 顶层的 pwa 模块选项,与 framework 的 defaults 深合并:

pwa: {
  manifest: {
    categories: ['entertainment'],
    shortcuts: [{ name: '我的', url: '/mine' }],
  },
},

⚠️ 别用它覆盖上表里那几个字段 —— 会与 framework.pwa 形成两个事实源,改一处不生效。

项目名称和主题色

starter 模板在 nuxt.config.ts 里已把这些字段逐条接好线:

// 顶层 pwa 是 @vite-pwa/nuxt 的模块选项,用它自带的类型,无需从 framework 引入
import swiftConfig from './swift.config.json';

pwa: {
  manifest: {
    name: swiftConfig.project.title, // 改站点标题一处即可
    short_name: swiftConfig.project.title, // 太长就在这里改短
    description: swiftConfig.project.description, // 与 SEO 同源
    theme_color: '#3b82f6', // UI 稿出来后改品牌主色
    background_color: '#ffffff', // 深色站必改
  },
},

直接改这里,不要写进 swift.config.json 的 framework.pwa —— 那会变成第二处事实源, project.title 改了它不会跟。framework.pwa 在 swift.config.json 里只承担布尔开关。

不接线会怎样:framework.pwa 传布尔 true 时,name / shortName 取占位默认 App, themeColor 取 #3b82f6 —— 装到手机主屏上就叫「App」。

添加到主屏幕引导

PWA 启用时,framework 会自动注册 <SwAddToHomescreenGuide> 组件, 仅需在 app.vue 挂载即可获得"H5 用户首次访问时引导添加到主屏"的能力。

通过顶层 addToHomescreen 选项可调整行为:

// nuxt.config.ts —— 🔴 **与 `pwa` 平级的顶层字段,不在 `pwa.manifest` 里**(fw#359 已平级化)
swiftConfig: {
  ...swiftConfig.framework,
  addToHomescreen: {
    delay: 2000,
    rememberFor: 'session',
  },
},

🔴 别写进 pwa.manifest:那是 Web App Manifest 的内容,vite-plugin-pwa 会把它 整份 Object.assign 后写进 manifest.webmanifest —— addToHomescreen 会变成清单里的 垃圾字段,而引导行为完全不生效(framework 根本不读那个位置)。

本项也不进 swift.config.json(CLI 不管它,与 auth / systemInfo 同类)。

只写你要改的字段即可,其余走默认:enabled 省略时跟随 framework.pwa, delay / rememberFor 各有默认值。

子选项类型默认说明
addToHomescreen.enabledboolean跟随 framework.pwa(可省略)显式关闭可保留 PWA 但不弹引导
addToHomescreen.delaynumber2000进站后延迟多久弹(毫秒)
addToHomescreen.rememberFor'session' | PeriodSpec'session'记忆策略,'session' 或 'Nm'·'Nh'·'Nd'(如 '7d')

完全不要引导:

swiftConfig: {
  ...swiftConfig.framework,
  addToHomescreen: false,
},

详细 API 见 SwAddToHomescreenGuide 文档。

开发模式注意事项

Service Worker 仅在生产构建中生效,pnpm dev 时不会注册 SW(需要 dev 期调试 SW 时,在 .env.local 设 SWIFT_WEB_PWA_DEV=true)。测试 PWA 功能需要:

pnpm build && pnpm preview
# 然后用 Chrome 开发者工具 → Application → Service Workers 验证