Engineering
Key takeaways
- A PWA conversion is additive: a manifest, a service worker and HTTPS sit on top of the site you already run and leave every URL resolving at its existing path.
- Caching strategy, not code, is where conversions fail; cache-first belongs to fingerprinted static assets only, and cart and checkout should never be cached at all.
- Version your cache names and ship an update path on day one, or a bad release can pin returning visitors to stale content that normal deployment cannot fix.
- Plan iOS as a separate phase, because Safari installs are manual through Add to Home Screen and its notification support does not match Android’s.
- Capture Lighthouse and Web Vitals baselines on your top templates before you touch anything, otherwise you cannot prove the conversion moved speed, installs or bounce rate.
Can you convert an existing website to a PWA without rebuilding it?
Yes. To convert a website to a PWA you add three things to the site you already run: a web app manifest, a registered service worker, and HTTPS on every environment. Templates, routes, CMS and URL structure stay exactly where they are. That is the most useful fact about PWA conversion, because it turns a project many owners price as a rebuild into an additive layer that can ship in days.
The additive part is also the easy part. Writing a manifest takes an afternoon. Deciding which of your four thousand URLs may be served from a cache, and for how long, is the work that separates a site that feels instant from one that shows a customer yesterday’s price.
That gap is the honest state of the field. Most sites touching PWA technology use a service worker for caching or push and never finish the installable experience. The half that gets skipped is usually the half that carries the visible benefit.
What does a PWA add to a site you already have?
Three components, doing three different jobs.
A service worker is a background script the browser runs separately from the page. It sits between your pages and the network, intercepts requests, and decides what comes from cache, what goes to the server, and what gets queued until connectivity returns. Offline access, instant repeat loads and push notifications all depend on it.
A web app manifest is a JSON file declaring the site’s name, icons, start URL and display mode. It is what lets a browser offer “Add to Home Screen” and launch your site in an app-like window with no browser chrome.
HTTPS is the precondition. Browsers refuse to register a service worker on an insecure origin, with localhost the only exception.
Adoption of the two halves has diverged sharply. Service worker use on mobile sites climbed from 1.4% in 2022 to 20.0% in 2025, while web app manifest adoption has sat near 9% and barely moved across the same period.1 Larger sites are further ahead: the top 1,000 sites reach 30.3% service worker adoption on desktop and 28.9% on mobile.1
What are the PWA conversion steps, in order?
1. Capture a baseline before you change anything
Run Lighthouse against your five highest-traffic templates and record Largest Contentful Paint, Interaction to Next Paint and Cumulative Layout Shift for each. Save the reports. Without that baseline you cannot prove afterwards that the conversion did anything, and speed is the commercial argument for the whole exercise.
2. Put a valid certificate on every environment, staging included
This is where teams lose whole afternoons. On a staging host with a self-signed or expired certificate, the browser declines to register the service worker and says very little about why. Engineers then debug the caching code, which is fine and irrelevant. Fix the certificate first, on production and on every preview and staging origin, then retest.
3. Write the manifest and generate the icons
Keep it minimal and correct. A 192px icon and a 512px icon are the practical floor, plus one maskable icon so Android does not letterbox your logo inside a white circle.
{
"name": "Example Retail",
"short_name": "Example",
"start_url": "/?source=pwa",
"display": "standalone",
"background_color": "#ffffff",
"theme_color": "#0b3d91",
"icons": [
{ "src": "/icons/icon-192.png", "sizes": "192x192", "type": "image/png" },
{ "src": "/icons/icon-512.png", "sizes": "512x512", "type": "image/png" },
{ "src": "/icons/maskable-512.png", "sizes": "512x512", "type": "image/png", "purpose": "maskable" }
]
}
Reference it from every page head with a single link tag pointing at /manifest.webmanifest. Add the start_url query parameter shown above and you get install attribution in analytics for free.
4. Register a service worker with versioned caches
Serve the file from the site root so its scope covers the whole origin. Name the cache with a version string and delete the old ones on activate, so a fix actually reaches people who already have the previous version installed.
const CACHE = 'shell-v7';
self.addEventListener('install', e => {
e.waitUntil(caches.open(CACHE).then(c => c.addAll(SHELL_ASSETS)));
self.skipWaiting();
});
self.addEventListener('activate', e => {
e.waitUntil(caches.keys().then(keys =>
Promise.all(keys.filter(k => k !== CACHE).map(k => caches.delete(k)))
).then(() => self.clients.claim()));
});
5. Assign a caching strategy per route, not per site
This is the decision that makes or breaks the conversion, and it gets its own section below.
6. Build the offline fallback and the update path
Precache one branded offline page and serve it when a navigation request fails. Then decide how updates reach users: either activate immediately, as the snippet above does, or show a small “new version available” prompt and reload on click. Choose one and write it into your release checklist alongside the rest of your software development life cycle gates.
7. Re-audit, then instrument
Re-run Lighthouse on the same five templates, compare against the saved baseline, and clear the installability failures it lists before launch. Then add events for install prompt shown, install accepted, and launches from the installed icon.
Which caching strategy should each route use?
Cache-first is the default in most tutorials and the wrong default for most commercial sites. It will happily serve a stale price, a sold-out product shown as in stock, or a logged-out header to a logged-in customer. Reserve it for assets whose filenames change when their contents change.
| Route or asset | Strategy | Reason |
|---|---|---|
| Fingerprinted CSS, JS, fonts, logo | Cache first | Contents never change under a given filename, so a cache hit is always correct. |
| Product, pricing and stock pages | Network first, cache as fallback | Correctness beats speed. A cached price is a customer service problem. |
| Articles, help centre, marketing pages | Stale while revalidate | Instant render from cache, quiet refresh behind it, next visit is current. |
| Cart, checkout, authentication, payment | Network only, never cached | Session-bound and commercially sensitive. Keep the service worker out of it. |
| Product imagery and media | Cache first with an expiry and a size cap | Large, rarely changed, and cheap to evict on a schedule. |
Write the rules down as a table like this one before anyone writes the fetch handler. On a site of any size, the argument about which routes are safe to cache is a merchandising and compliance conversation rather than a web development one, and it is far cheaper to have on paper.
Why does the install prompt not appear?
Nearly always one of these, and in roughly this order of frequency.
- The origin is not fully secure. An expired certificate, or one mixed-content asset loaded over plain HTTP, is enough to block registration.
- The manifest is incomplete. Missing name or short_name, a start_url outside the service worker scope, no 192px or 512px icon, or a display value other than standalone, fullscreen or minimal-ui.
- The service worker scope is too narrow. A worker served from /assets/sw.js controls only /assets/. Serve it from the root, or set the Service-Worker-Allowed header.
- There is no fetch handler. A worker that registers but never responds to a fetch event does not satisfy installability.
- An old worker is still in control. Without skipWaiting and clients.claim, the previous version keeps serving until every tab is closed, which on a phone can mean weeks.
- The browser has already offered it. Prompt eligibility depends on device and installation state, so test on a clean profile rather than the one you have been developing against.
A service worker is the only part of your stack that keeps running after a bad deploy, which is precisely why it can keep serving one.
The fifth item is the expensive one. Ship an unversioned cache and a caching bug becomes unfixable through normal deployment, because the broken worker intercepts the request for its own replacement.
What does iOS Safari still not do?
Enough that it belongs in the plan rather than the retrospective. iOS has no automatic install prompt: users install through the Share menu and “Add to Home Screen”, which means adoption depends on you teaching them, usually with a dismissible in-page banner shown only to Safari on iPhone. Notification support does not match Android’s either, so a rollout that promises push parity across both platforms in the same phase will miss.
Plan iOS as its own phase with its own success measure. Where notifications are core to the product rather than a nice addition, the honest answer is often a native mobile app for iOS alongside the PWA for everyone else, rather than a single build that disappoints on one platform.
Does converting your website to a PWA hurt SEO or change your URLs?
No, if you do it as described. A manifest and a service worker are additive layers over your existing pages. Every URL keeps resolving at the same path, every canonical tag stays valid, and there is no migration, no redirect map and no re-indexing event. That is what makes PWA conversion unusually low-risk compared with a replatform.
Two things do need care. First, if the service worker serves a cached shell to a crawler, make sure the crawled HTML still contains the real content and not a loading state. Second, keep start_url as a real, indexable page rather than an app-only route, and let the query parameter carry the attribution. Anything you build around this belongs inside your normal release process, with the same review and rollback steps as any other change.
How do you measure whether the conversion worked?
Compare the same five templates before and after: Lighthouse performance and installability scores, then field Web Vitals from real users over the following month. Add three product metrics a PWA should move if it is doing its job, namely pages per session, bounce rate, and repeat visits from installed users.
Twitter Lite remains the clearest public benchmark for what a well-executed conversion can do at scale. After launch it cut data usage by up to 70%, lifted pages per session by 65% and Tweets sent by 75%, and reduced bounce rate by 20%.2
Those are best-case numbers from a team with unusual resources, so treat them as a ceiling rather than a forecast. The mechanism behind them is ordinary, though: faster pages hold more sessions. Bounce probability rises 32% as load time goes from one second to three, and 123% as it goes from one second to ten.4 A service worker removes most of the network from a repeat visit, which is why the effect shows up more reliably in the second and third session than in the first.
The tooling around all of this is now commodity, and the direction of spend follows. The market for progressive web apps was estimated at USD 2.08 billion in 2024 and is projected to reach USD 21.24 billion by 2033, a compound annual growth rate of 29.9%.5 If you want a second opinion on which of your routes are safe to cache before you commit to a strategy, that is a short conversation worth having early: talk to our engineering team.
Frequently asked questions
Do you need HTTPS to make a site a PWA?
Yes. Browsers refuse to register a service worker on an insecure origin, with localhost the only exception. That rule applies to staging and preview environments too, so a self-signed or expired certificate on staging will make the service worker silently fail to register. Fix certificates before debugging any caching code.
What is the difference between a service worker and a web app manifest?
A service worker is a background script the browser runs separately from the page. It intercepts network requests and decides what is served from cache, which is what enables offline access and push notifications. A web app manifest is a JSON file declaring the site's name, icons, start URL and display mode, which is what lets the browser offer Add to Home Screen and launch the site in an app-like window. Installability needs both.
Will a PWA replace my native iOS or Android app?
Sometimes, and it depends on what the app does. A PWA covers browsing, content, accounts and most transactional flows from a single codebase. It does not match native access to deeper device capability, and notification support on iOS does not match Android's. Where notifications or device hardware are core to the product on iOS, a native build alongside the PWA is usually the more honest plan.
Do PWAs work offline for e-commerce checkout flows?
They should not try. Cart, authentication, payment and checkout are session-bound and change per request, so they belong on a network-only path with the service worker excluded. Offline support is best spent on the static shell, product content and a branded offline page. Serving a cached price or a cached stock level creates a customer service problem that outweighs the speed gain.
What happens to push notifications on iOS PWAs?
Support on iOS Safari is partial and does not reach parity with Android. Installs also happen manually through the Share menu and Add to Home Screen rather than an automatic browser prompt. Plan iOS as its own phase with its own success measure, and avoid promising both platforms the same notification behaviour in the same release.
How do you test whether a website qualifies as an installable PWA?
Run Lighthouse on the templates that carry most of your traffic and read the installability section, which lists each failing requirement. Then check the application panel in browser developer tools to confirm the manifest parses, the icons load, and the service worker is registered and controlling the page. Always test on a clean browser profile, because a device that has already been offered the install prompt will not show it again.
Sources
- HTTP Archive: Web Almanac 2025, Progressive Web Apps chapter, 2025. almanac.httparchive.org
- web.dev (Google): Twitter Lite case study, 2024. web.dev
- Google/DoubleClick via Marketing Dive: mobile load time abandonment research, 2024. marketingdive.com
- Google: Mobile page speed industry benchmarks, 2024. business.google.com
- Grand View Research: Progressive Web Apps market, 2025. grandviewresearch.com




