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)
/apirequests: 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 iconicon-512.png— High-resolution icon + splash screenapple-touch-icon.png— iOS home screen icon
The framework injects the manifest link for you
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}" />
hreffollowsapp.baseURL(sub-path deployments resolve correctly) and honours a top-levelvite-pwa.filenameoverride- If the applier already declares
link[rel=manifest]or that meta innuxt.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=trueto 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:
| Source | Who reads it |
|---|---|
apple-mobile-web-app-title meta | Every iOS version; already-installed legacy web clips only read this |
Manifest short_name | iOS 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 manifestdisplayfield and marked deprecated on MDN) orapple-mobile-web-app-status-bar-style(a visual decision —black-translucentpushes 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 field | Source | Notes |
|---|---|---|
name | project.title in swift.config.json (wired via nuxt.config) | Full name. Same source as the site title — change it in one place |
short_name | top-level pwa.manifest.short_name in nuxt.config.ts | The name under the home screen icon, and the iOS prefill. Defaults to the title; shorten it here past ~12 characters |
description | project.description in swift.config.json (wired via nuxt.config) | Same source as the SEO description |
theme_color | top-level pwa.manifest.theme_color in nuxt.config.ts | Android task card / system chrome. Set it once the design lands |
background_color | top-level pwa.manifest.background_color in nuxt.config.ts (default #ffffff) | The colour painted during cold start — required for dark sites |
lang | Automatic, from framework.i18n.defaultLocale | No extra config surface |
display / start_url / icons | Fixed by the framework | See 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 innuxt.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.pwais the booleantrue,name/shortNamefall back to the placeholderAppandthemeColorto#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, andvite-plugin-pwawrites it verbatim intomanifest.webmanifest—addToHomescreenwould become a junk field there while the guide behaviour does nothing (the framework never reads that location). It also does not belong inswift.config.json(the CLI does not manage it). Write only the fields you want to change: omittingenabledfollowsframework.pwa;delay/rememberForhave defaults.
| Sub-option | Type | Default | Description |
|---|---|---|---|
addToHomescreen.enabled | boolean | follows pwa.enabled | Disable to keep PWA but skip the guide |
addToHomescreen.delay | number | 2000 | Delay (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