PWA Offline Support

Implements a Progressive Web App via @vite-pwa/nuxt, with automatic Service Worker generation, offline caching, and an add-to-home-screen prompt.

Enable PWA

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

Automatic Service Worker Generation

When PWA is enabled, a Service Worker (Workbox) is automatically generated at build time with the following caching strategies:

  • JS / CSS / fonts (woff2): build-time precache (skipWaiting + clientsClaim, new deploys take effect on the next request)
  • Images: Stale-While-Revalidate (serve from cache, refresh in background; 30 days / max 60 entries)
  • /api requests: NetworkFirst (network first, falls back to cache after a 10s timeout)
  • Page HTML: not handled by the Service Worker (navigateFallback: null, always served by the network / Nitro)

Web App Manifest

The install experience ("Add to Home Screen") depends on manifest.json, which the framework generates automatically. Place icon files in the public/icons/ directory:

  • icon-192.png — Android home screen icon
  • icon-512.png — High-resolution icon + splash screen
  • apple-touch-icon.png — iOS home screen icon

Generating the manifest file and linking it into <head> are two separate things. @vite-pwa/nuxt only registers VitePwaManifest as an auto-importable component — the app has to mount it before <link rel="manifest"> is emitted. Miss that, and the manifest file is still generated and the service worker still registers, but no browser ever reads it.

The framework injects the tags into app.head at build time (fw#355), so appliers do not need to mount <VitePwaManifest />:

<link rel="manifest" href="/manifest.webmanifest" />
<meta name="apple-mobile-web-app-title" content="{pwa.shortName}" />
  • href follows app.baseURL (sub-path deployments resolve correctly) and honours a top-level vite-pwa.filename override
  • If the applier already declares link[rel=manifest] or that meta in nuxt.config, the framework does not inject a duplicate
  • Not injected in dev by default — @vite-pwa does not emit a manifest file in dev, so the link would 404. Set SWIFT_WEB_PWA_DEV=true to enable both the manifest and this link locally

Where the iOS "Add to Home Screen" name comes from

iOS has two sources for the home screen name and their precedence is version dependent, so the framework feeds both:

SourceWho reads it
apple-mobile-web-app-title metaEvery iOS version; already-installed legacy web clips only read this
Manifest short_nameiOS versions with manifest support (requires the link above)

Both values come from pwa.manifest.short_name, so they can never disagree. Without that meta and without a linked manifest, iOS falls back to the full page <title> — exactly the symptom in fw#353.

The framework deliberately does not inject apple-mobile-web-app-capable (superseded by the manifest display field and marked deprecated on MDN) or apple-mobile-web-app-status-bar-style (a visual decision — black-translucent pushes content under the status bar, so the applier should own it).

When you need those, or want the project itself to own the home screen name, declare them in app.head.meta in nuxt.config.ts — the framework skips its own injection whenever it finds a meta of the same name (idempotent):

app: {
  head: {
    meta: [
      // Take over the home screen name (otherwise framework injects pwa.manifest.short_name)
      { name: 'apple-mobile-web-app-title', content: 'SnapBay' },
      // ⚠️ black-translucent pushes content under the status bar — reserve the safe area
      //    yourself: padding-top: env(safe-area-inset-top) on your top container
      { name: 'apple-mobile-web-app-status-bar-style', content: 'black-translucent' },
    ],
  },
},

To rename, prefer pwa.manifest.short_name in swift.config.json — it is the same source as the manifest short_name, so iOS and Android change together.

Where each manifest field comes from

manifest fieldSourceNotes
nameproject.title in swift.config.json (wired via nuxt.config)Full name. Same source as the site title — change it in one place
short_nametop-level pwa.manifest.short_name in nuxt.config.tsThe name under the home screen icon, and the iOS prefill. Defaults to the title; shorten it here past ~12 characters
descriptionproject.description in swift.config.json (wired via nuxt.config)Same source as the SEO description
theme_colortop-level pwa.manifest.theme_color in nuxt.config.tsAndroid task card / system chrome. Set it once the design lands
background_colortop-level pwa.manifest.background_color in nuxt.config.ts (default #ffffff)The colour painted during cold start — required for dark sites
langAutomatic, from framework.i18n.defaultLocaleNo extra config surface
display / start_url / iconsFixed by the frameworkSee above

Why two places: whether PWA is on lives in swift.config.json (sw config set framework.pwa true / false, managed by the CLI; it no longer installs/removes @vite-pwa/nuxt — the framework carries that module); what it is called and what colour it is lives in nuxt.config.ts. When you scaffold a project you only have a site name — the description is not settled and the design has not landed, so nobody can answer "what colour?" yet; and an uncommented, invisible field in a JSON file is one nobody comes back to fill in. The starter template wires each of these fields with an explanatory comment, so you see them when you edit.

🔴 Dark sites must set backgroundColor

When a PWA cold-starts from the home screen icon, the system paints background_color full-screen before the first frame. The default is #ffffff, so a dark site flashes white first.

That flash only appears for an instant on a real device on a cold start — you will almost never hit it on desktop or in dev, which is exactly why the framework exposes a knob instead of leaving it to each project. Use your site's main background colour:

pwa: {
  manifest: {
    // …
    background_color: '#0a1213', // your dark site's main background
  },
},

Everything else: the top-level pwa escape hatch

Fields outside the table (categories / shortcuts / screenshots, custom icons, …) go through the top-level pwa module options in nuxt.config.ts, deep-merged over the framework defaults:

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

⚠️ Do not use it to override the fields above — that creates a second source of truth against framework.pwa, and edits in one place stop taking effect.

App Name and Theme Color

The starter template already wires these fields one by one in nuxt.config.ts:

// The top-level `pwa` is @vite-pwa/nuxt's own module option — it carries its own
// types, so nothing needs to be imported from framework here.
import swiftConfig from './swift.config.json';

pwa: {
  manifest: {
    name: swiftConfig.project.title, // change the site title in one place
    short_name: swiftConfig.project.title, // shorten it here if the title is long
    description: swiftConfig.project.description, // same source as SEO
    theme_color: '#3b82f6', // set to your brand colour once the design lands
    background_color: '#ffffff', // dark sites must change this
  },
},

Edit here, not in framework.pwa inside swift.config.json — that would become a second source of truth, and changing project.title would not follow. In swift.config.json, framework.pwa is only a boolean switch.

What happens if you skip the wiring: when framework.pwa is the boolean true, name / shortName fall back to the placeholder App and themeColor to #3b82f6 — the icon on the home screen is literally labelled "App".

Add-to-Home-Screen Guide

When PWA is enabled, the framework automatically registers the <SwAddToHomescreenGuide> component. Mount it in app.vue to get an "first-visit guide on H5 to add the site to home screen" experience.

Adjust behavior via the addToHomescreen sub-option:

// nuxt.config.ts — 🔴 **a top-level field alongside `pwa`, NOT inside `pwa.manifest`** (flattened in fw#359)
swiftConfig: {
  ...swiftConfig.framework,
  addToHomescreen: {
    delay: 2000,
    rememberFor: 'session',
  },
},

🔴 Do not put this inside pwa.manifest: that object is the Web App Manifest content, and vite-plugin-pwa writes it verbatim into manifest.webmanifest — addToHomescreen would become a junk field there while the guide behaviour does nothing (the framework never reads that location). It also does not belong in swift.config.json (the CLI does not manage it). Write only the fields you want to change: omitting enabled follows framework.pwa; delay / rememberFor have defaults.

Sub-optionTypeDefaultDescription
addToHomescreen.enabledbooleanfollows pwa.enabledDisable to keep PWA but skip the guide
addToHomescreen.delaynumber2000Delay (ms) after visit before showing
addToHomescreen.rememberFor'session' | PeriodSpec'session'Memory strategy: 'session' or 'Nm'·'Nh'·'Nd' (e.g. '7d')

To completely disable the guide:

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

See SwAddToHomescreenGuide docs for the full API.

Development Notes

The Service Worker is only active in production builds — it is not registered during pnpm dev (to debug the SW in dev, set SWIFT_WEB_PWA_DEV=true in .env.local). To test PWA functionality:

pnpm build && pnpm preview
# Then verify in Chrome DevTools → Application → Service Workers