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, standalone display, 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 */ ],
  },
})
FieldRequiredDefaultWhat it does
nameYesThe app name shown at install time and in the OS launcher.
shortNameNonameShorter label for home screens where space is tight.
themeColorNo#0a0e1aBrowser / OS chrome color; also emitted as <meta name="theme-color">.
backgroundColorNothemeColorSplash-screen background while the app boots.
descriptionNoDescription shown in install UIs and app listings.
iconsNo/icons/pwa-192.png, /icons/pwa-512.png, /icons/pwa-maskable-512.pngManifest 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.

ModePrecachedPages offline
apex build (static)The entire dist tree — pages, hashed assets, public files.Full offline — everything is served from the cache.
apex build --islandsThe entire dist tree — pages, hashed assets, public files.Full offline — everything is served from the cache.
apex build --serverHashed 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 Workerssw.js should 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.
HTTPS required

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/core 0.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.js and 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.