Turning a web application into something a user can install takes surprisingly little: a manifest describing the application, a service worker, and icons at the right sizes. The browser does the rest.
The hard part is not the plumbing. It is deciding what your application should do when the network is not there.
The manifest is a promise
Declaring display: standalone removes the browser chrome. That means removing the back button, the address bar and the reload button. If your application relies on any of them, you have just broken it, and only on installed devices, which is where you will not be testing.
Same for start_url: it is where the application opens, not where the user last was. Point it at something that makes sense cold.
Offline is a product decision
A service worker can serve from cache, from the network, or race the two. Which one is right depends entirely on the page:
- Fonts, icons, compiled assets: cache first. They change only when you deploy.
- Content pages: network first, cache as a fallback. A stale page beats an error page.
- Anything with a price, a stock level or a permission: network only. Serving a cached answer here is worse than serving nothing.
That last line is the one that gets skipped, and it is the one that causes real incidents.
Test it the way it fails
Switching the browser to offline is not a real test. The interesting failure is the slow, flaky connection where requests hang instead of failing, because that is where a cache-first strategy silently serves month-old data. Throttle rather than disconnect.
pwa-bundle generates the manifest, the icons and the service worker for a Symfony application, and leaves the strategy decisions to you, where they belong.