Ch21.4: Publishing a PWA
1. Hosting
A PWA is static files: HTML, CSS, JavaScript, a manifest, icons and a
.wasm binary. Any static host works. The two that matter for this book are
GitHub Pages and Cloudflare Pages, because they differ in exactly the place that matters
for a global audience.
GitHub Pages is the simplest. Push the site to a repository, enable Pages,
and you get HTTPS on <user>.github.io/<repo>/ or a custom domain.
The problem is reachability: GitHub Pages is frequently blocked or unreliable in mainland
China, and so are many GitHub-hosted assets. If your audience is there, a GitHub Pages URL
is not a distribution strategy.
Cloudflare Pages is the practical alternative. Connect the repository, set the build command and output directory, and Cloudflare serves the result from its CDN with HTTPS and a custom domain. It is generally reachable in mainland China, which makes it the better default for a global PWA. Test from the target network before you announce the URL; "generally" is not a guarantee.
Other options are Netlify, Vercel, Firebase Hosting and a self-hosted static server. The requirements are the same: HTTPS, the right MIME types, and cache headers that do not fight the service worker.
2. The Details That Break Deployments
-
MIME types.
.wasmmust be served asapplication/wasm, orinstantiateStreamingwill refuse it. The manifest should beapplication/manifest+json;.webmanifestis usually mapped correctly, but check. -
Service worker caching. Serve
sw.jswithCache-Control: no-cacheso the browser can detect updates. Hashed static assets can be immutable; the HTML entry point should not be. -
Scope and base path. If the app lives under a subpath, the manifest's
start_urlandscope, the service worker registration scope, and every absolute URL must agree. A PWA at/notes/that registers/sw.jsat the origin root will control more than it should. -
HTTPS and mixed content. A service worker requires a secure context. One
http://asset can break the install or the offline shell. - Cache busting. Bump the service worker cache version on every release, and use hashed file names for the assets that change.
3. Packaging for App Stores
PWA Builder takes a PWA URL and generates packages for the Microsoft Store, Google Play, the iOS App Store and Meta Quest. It reads the manifest, checks installability, and wraps the same web app in a native shell: MSIX for Windows, a Trusted Web Activity for Android, a WKWebView wrapper for iOS.
This is a pragmatic bridge, not a cure for the duopoly. The code stays the same, but the store's review, rules and revenue share come back. The wrapper is a shell around the PWA; the web app is still the thing you maintain. Use it when store presence is a requirement, and keep the URL installable on its own.
Before packaging, make sure the PWA passes the installability checks: a valid manifest, 192 and 512 icons, a service worker with a fetch handler, HTTPS, and a start URL inside the scope. PWA Builder will tell you what is missing.
4. The PWA Store
PWA Store is a free, open-source launcher for PWAs and PWA wrappers. It is itself a PWA, it is GPLv3, and it accepts wrappers for sites that do not ship PWA support. The store is curated but open; the point is that distribution does not require a platform owner's permission.
The easiest way to get listed is to send the URL of your PWA. It will be added manually. If you prefer to do it yourself, submit a pull request to github.com/trcrsired/pwa-store. The store's browse page is pwa-store-7x5.pages.dev/store/.
This is a different distribution model from an app store: a URL, a manifest and a service worker are enough to install, and the store is a discovery layer rather than a gatekeeper. That is the model the chapter has been arguing for.
5. Launch Checklist
- HTTPS works; no mixed content.
- Manifest is valid: name, short name, start URL, scope, display, 192 and 512 icons.
- Icons include a maskable variant; screenshots are present if the install UI uses them.
- Service worker registers with the correct scope and is activated.
- App shell is pre-cached; offline start works with the network disabled.
- Cache version is bumped; an update actually reaches an installed client.
sw.jsis served withno-cache;.wasmis served asapplication/wasm.- Install tested on Android, iOS and desktop; standalone mode looks right.
- Reachability tested from mainland China; Cloudflare Pages preferred over GitHub Pages.
- Privacy policy, license and source repository are linked.