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 會把它整份寫進 manifest.webmanifest —— addToHomescreen 會變成清單裡的垃圾欄位,而引導行為完全不生效。 本項也不進 swift.config.json(CLI 不管它)。只寫你要改的欄位即可:enabled 省略時跟隨 framework.pwa,delay / rememberFor 各有預設值。

子選項類型預設說明
addToHomescreen.enabledboolean跟隨 pwa.enabled顯式關閉可保留 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 驗證