PWA
Installable & offline.
Declare a pwa block in apex.config.ts and apex build makes the
app a Progressive Web App — a generated manifest.webmanifest, a small
service worker, and the head tags to wire them up, in every build mode.
🟡 Experimental — shipped in @apex-stack/core 0.40.0.
What you get #
Add a pwa block to apex.config.ts and apex build makes the app
installable and offline-capable. The build emits two files into dist/:
manifest.webmanifest— name, short name,standalonedisplay, icons, and theme / background colors, generated from your config.sw.js— a small generated service worker (precache-and-offline). The cache name is content-hashed, so a new deploy activates a fresh cache and cleans the old one.
The HTML shells automatically get <link rel="manifest">,
<meta name="theme-color">, and a
navigator.serviceWorker.register('/sw.js') script — in both the default
(prerendered / SSR) and islands modes. No template edits, no plugin config.
Why not vite-plugin-pwa? Apex generates its own ~60-line worker instead: Apex has no
index.html for a plugin to transform (HTML shells are string-built) and the precache list
is fully known at build time — so the generated worker does the whole job with zero new dependencies.
Quickstart #
One command wires everything up, then a normal build produces the PWA.
apex extend pwa # adds the pwa: block to apex.config.ts + scaffolds default icons
apex build # → dist/manifest.webmanifest + dist/sw.js, head tags injected
apex extend pwa scaffolds default icons — public/icons/pwa-192.png,
pwa-512.png, and pwa-maskable-512.png, generated from the Apex mark — swap
them for your brand. PWA is also offered as a prompt in apex new (or pass
--pwa).
Already using custom icons for mobile? apex mobile android --icon your-icon.png also
emits the three PWA sizes into public/icons/.
The pwa config block #
Everything lives under one key in apex.config.ts; only name is required.
export default defineConfig({
pwa: {
name: 'My Apex App', // required
shortName: 'App', // optional (defaults to name)
themeColor: '#0a0e1a', // optional (this is the default)
backgroundColor: '#0a0e1a', // optional (defaults to themeColor)
description: '…', // optional
icons: [ /* optional — defaults to /icons/pwa-{192,512,maskable-512}.png */ ],
},
})
| Field | Required | Default | What it does |
|---|---|---|---|
name | Yes | — | The app name shown at install time and in the OS launcher. |
shortName | No | name | Shorter label for home screens where space is tight. |
themeColor | No | #0a0e1a | Browser / OS chrome color; also emitted as <meta name="theme-color">. |
backgroundColor | No | themeColor | Splash-screen background while the app boots. |
description | No | — | Description shown in install UIs and app listings. |
icons | No | /icons/pwa-192.png, /icons/pwa-512.png, /icons/pwa-maskable-512.png | Manifest icon set; the defaults are the files apex extend pwa scaffolds. |
What each build mode emits #
All three build modes get the manifest, the worker, and the injected head tags — what differs is what's precached and how pages are served offline.
| Mode | Precached | Pages offline |
|---|---|---|
apex build (static) | The entire dist tree — pages, hashed assets, public files. | Full offline — everything is served from the cache. |
apex build --islands | The entire dist tree — pages, hashed assets, public files. | Full offline — everything is served from the cache. |
apex build --server | Hashed assets + public files; pages are dynamic. | Navigations go network-first with a cache fallback. |
On the server target, the prod server (apex start) serves
/manifest.webmanifest with the application/manifest+json MIME type and
/sw.js with Cache-Control: no-cache — so deploys propagate — and injects the
head tags at runtime.
PWA is build-only: apex dev is untouched, and no worker is registered in
dev.
How the worker behaves #
The generated worker is deliberately simple — precache-and-offline, nothing configurable to break.
- Precache on install — the build writes the exact precache list into the worker; on install it caches every listed URL.
- Cache-first assets — precached files (hashed assets, public files, and in static / islands builds the pages themselves) are served straight from the cache.
- Network-first navigations — on the server target, page navigations try the network and fall back to the cache when it's unreachable.
- Content-hashed cache name — the cache name is derived from the precached content, so a new deploy installs into a fresh cache, activates it, and deletes the old one. No stale-forever apps.
Verify it #
Build, serve, and check it the way a browser does.
apex build
apex start # or any static host for the static / islands builds
- Open DevTools → Application → Manifest — the manifest should parse with your name, icons, and colors — and Application → Service Workers —
sw.jsshould be activated. - Run a Lighthouse PWA audit against the served app.
- In Chrome, the install prompt appears in the address bar.
- Load the app, go airplane mode, reload — the page still renders.
Service workers only register on HTTPS or localhost. apex start on
your machine works out of the box; in production put the app behind TLS.
Scope & status #
Being precise about the edges matters more than the pitch. Here's the honest framing.
- 🟡 Experimental — shipped in
@apex-stack/core0.40.0; the config surface may still move. - Build-only — no service worker in
apex dev; you verify PWA behavior against a build. - No runtime-caching config yet — the worker's strategy (precache, cache-first assets, network-first navigations) is fixed; there's no per-route caching DSL.
- Scoped to the site root — the worker registers at
/sw.jsand controls the whole origin; apps served under a sub-path aren't covered yet.
The PWA build wraps the same app as every other target — see Build & deploy for the three build modes, and Mobile for the on-device APK story.