Moving Your App to a Progressive Web App: What to Know
Turn an existing web app into a PWA with a manifest, HTTPS, and carefully chosen offline behavior—without rebuilding it as a native app.
Short answer: You can usually make an existing web app a progressive web app (PWA) incrementally. Keep the web app, add a linked web app manifest, serve it over HTTPS, and decide whether a service worker should provide specific offline or caching behavior. A PWA is still a website; it does not require a native rewrite or a single-page architecture. Browser installation and capabilities vary, so test the browsers and devices your users rely on.
1. What “moving your app to a PWA” means
In this guide, “moving” means enhancing an existing web app so supporting browsers can offer installation and so you can add selected app-like capabilities. It does not mean moving the app to a new host or rebuilding it as a native application.
PWA and single-page app describe different things. A PWA can use a traditional multi-page site or a single-page architecture. Keep the existing browser experience usable: installation is an enhancement, not a replacement for ordinary navigation and access. MDN’s PWA overview describes progressive enhancement and the role of service workers.
2. What you need for an installable PWA
Start with a web app manifest linked from your HTML and production HTTPS. Exact installation criteria vary by browser. For Chromium-based browsers, MDN lists these manifest fields among the installability requirements:
nameorshort_name- Icons at 192 by 192 and 512 by 512 pixels
start_urldisplayordisplay_overrideprefer_related_applicationsomitted or set tofalse
Serve the production app over HTTPS. Local development on localhost or 127.0.0.1 is an exception. A static web server can be enough for a frontend-only app; use your existing application host if it already serves HTTPS. See MDN’s installability guide and Microsoft’s PWA hosting guidance.
3. Add a manifest to the existing app
Create a JSON manifest, for example at /manifest.webmanifest. Replace the example name, URL, and icon paths with assets and routes that exist in your app.
{
"id": "/",
"name": "Example Project Manager",
"short_name": "Projects",
"description": "Manage projects and tasks.",
"start_url": "/?source=pwa",
"scope": "/",
"display": "standalone",
"background_color": "#ffffff",
"theme_color": "#17324d",
"prefer_related_applications": false,
"icons": [
{
"src": "/icons/app-192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "/icons/app-512.png",
"sizes": "512x512",
"type": "image/png"
}
]
}
The id, description, colors, and scope are useful manifest metadata and configuration, but browser install requirements differ. Make sure start_url is a valid route inside the app and that the icons are publicly reachable. Choose a scope that covers the routes intended to belong to the installed app.
Link the manifest in the document head:
<link rel="manifest" href="/manifest.webmanifest">
For a multi-page app, include the link on every page. Confirm the manifest is served as JSON and can be fetched without authentication or a redirect to an unrelated page.
4. Decide whether you need a service worker
A service worker is not required just to install a PWA. Add one when you have a defined reliability goal, such as showing an offline page or caching selected static assets. It can intercept network requests and manage cached resources, but installation alone does not make every application feature work offline.
Before writing caching rules, list the user tasks that should work with no connection. For many apps, a useful first step is a custom offline page that explains the connection is unavailable. Avoid caching authenticated or user-specific responses unless you have designed and reviewed the privacy, freshness, and logout behavior. See MDN’s offline and background operation guidance.
A minimal service worker can provide a clear offline fallback for navigations. This example intentionally does not cache application data or scripts; extend it only when you have defined which resources can safely be reused.
// /sw.js
const CACHE_NAME = "example-shell-v1";
const OFFLINE_URL = "/offline.html";
self.addEventListener("install", (event) => {
event.waitUntil(
caches.open(CACHE_NAME).then((cache) => cache.add(OFFLINE_URL))
);
self.skipWaiting();
});
self.addEventListener("activate", (event) => {
event.waitUntil(
caches.keys().then((keys) =>
Promise.all(
keys
.filter((key) => key !== CACHE_NAME)
.map((key) => caches.delete(key))
)
)
);
self.clients.claim();
});
self.addEventListener("fetch", (event) => {
const request = event.request;
if (request.method !== "GET") return;
if (request.mode !== "navigate") return;
event.respondWith(
fetch(request).catch(async () => {
const cached = await caches.match(OFFLINE_URL);
return cached || new Response("You are offline. Try again when connected.", {
status: 503,
headers: { "Content-Type": "text/plain; charset=utf-8" }
});
})
);
});
Register the worker from your app’s JavaScript. Feature detection keeps the page functional in browsers without service worker support.
if ("serviceWorker" in navigator) {
window.addEventListener("load", () => {
navigator.serviceWorker.register("/sw.js", { scope: "/" })
.catch((error) => console.error("Service worker registration failed:", error));
});
}
Place offline.html at the path used above. A service worker’s scope is constrained by its script location unless the server sends an appropriate Service-Worker-Allowed header. Deploying a revised worker can also leave users with an older cached version until the new worker activates. Version caches deliberately and test update behavior.
5. Preserve fallbacks and define the offline promise
Write down what users can and cannot do in each network state. A cached page shell may load while API-backed actions still fail. If a task requires the server, explain that clearly and avoid displaying stale data as if it were current.
- Keep core pages and interactions usable in a normal browser tab.
- Use feature detection for advanced browser APIs and provide acceptable fallbacks.
- Show an offline state that explains what is unavailable and what the user can do next.
- Test fresh installs, returning visits, lost connections, restored connections, and app updates.
- Do not assume that successful installation proves that offline behavior is correct.
MDN’s PWA best practices recommend testing compatibility and providing fallbacks for advanced APIs.
6. Test installation on your users’ platforms
Installation prompts and entry points are not uniform. The platform support summarized by MDN includes Chromium browsers on supported desktop operating systems, Add to Dock in Safari 17 and later on macOS Sonoma and later, and Share-menu installation on iOS 16.4 and later in Safari, Chrome, Edge, Firefox, and Orion. Firefox desktop does not support manifest-based PWA installation according to the cited MDN guide. On Android, Chrome on devices with Google Mobile Services and Samsung Internet on Samsung devices can install PWAs as WebAPKs; other cases may create a browser-badged home-screen shortcut.
These are version-sensitive details. Check current browser requirements before release and test on the actual browsers, operating systems, and devices in your audience. Confirm the launch route, icon appearance, standalone display, navigation, and offline fallback where applicable. MDN’s installability guide tracks the platform distinctions.
7. A practical migration sequence
- Audit the current app. Record its routes, authentication behavior, static assets, API dependencies, deployment setup, and the user tasks that matter offline.
- Keep the browser experience healthy. Confirm that users can reach core content and actions without installing anything.
- Add icons and the manifest. Link the manifest on every page, verify its paths and fields, and choose a valid launch route and scope.
- Enable production HTTPS. Verify the deployed site and manifest are served securely. Keep local development on localhost.
- Choose a service-worker goal. If you need an offline message or specific caching, implement only that behavior first. A worker is optional for installation.
- Test platform behavior. Check installation entry points and browser fallbacks on the environments your users use.
- Release and observe failures. Check manifest loading, worker registration, stale caches, and offline states after deployment; revise cache versions when assets change.
8. Performance, reliability, and cost
A PWA is not a guaranteed performance improvement. The sources establish implementation capabilities, not a general speed or engagement gain. Measure your app’s existing load and interaction performance, then compare after changes on representative devices and network conditions.
Service-worker caching can reduce repeat network work for resources you deliberately cache, but it introduces cache invalidation and freshness decisions. Cache only what supports a stated user need, remove obsolete cache versions, and test deployments with both a fresh profile and an already-installed app. The migration cost depends on your app’s routes, hosting, assets, authentication, offline requirements, and testing matrix; there is no defensible generic cost estimate for an unspecified app.
9. Troubleshooting
| Symptom | Likely cause | What to check or fix |
|---|---|---|
| No install option appears | The browser does not support that installation flow, the site is not secure, or manifest criteria are missing. | Use HTTPS in production; inspect the manifest, required icon sizes, start URL, display setting, and browser-specific requirements. |
| Manifest fails to load | Wrong link path, invalid JSON, authentication redirect, or incorrect server response. | Open the manifest URL directly, validate the JSON, and ensure the link is present on each page and the file is publicly fetchable. |
| App launches on the wrong page | start_url is invalid, outside the app’s intended route, or handled differently by the server. |
Choose a reachable route within the manifest scope and test it in a fresh installation. |
| Icons are missing or look wrong | Icon URLs are broken, dimensions do not match declarations, or assets are unsuitable. | Verify both files load and that their actual dimensions and MIME type match the manifest entries. |
| Service worker registration fails | Production is not in a secure context, the script path is wrong, or server access is blocked. | Check HTTPS, the exact /sw.js response, browser console errors, and script scope. |
| Changes do not appear after deployment | An older worker or cache is still serving assets. | Version the cache, confirm the updated worker activates, and test update behavior across repeat visits. |
| Offline page appears but app actions fail | The fallback page is cached, but API data and operations were not designed for offline use. | Set clear offline expectations. Add deliberate data handling only if the product requires it, including conflict and freshness behavior. |
| Install differs across devices | Browser and operating-system installation support differs. | Test the exact audience platforms and document the available installation path; do not assume one prompt works everywhere. |
10. Do you need a native app or app-store package?
A PWA can be distributed through the web. Store packaging is a separate choice with store-specific steps; it is not required to add a manifest, secure hosting, or selected service-worker behavior. MDN identifies PWABuilder as a tool that can simplify packaging and publishing. Whether a PWA or native approach fits better depends on required device capabilities, offline behavior, distribution needs, code reuse, and platform-specific features.
Or skip the browser setup
If you need screenshots of your PWA pages for reviews or documentation, ScreenshotNeo captures a URL with one API request. Its cookie/consent banner handling, newsletter pop-up removal, and chat-widget removal run before capture and can each be turned off. Bot checks, blank pages, failed loads, timeouts, and cache hits are not billed; response headers report the page verdict and billing status. Its MCP server provides take_screenshot, get_page_info, and capture_pdf for AI agents.
See the ScreenshotNeo API documentation. This cURL example saves a WebP screenshot of a page:
curl -G "https://api.screenshotneo.com/v1/shot" \
-d access_key=YOUR_API_KEY \
--data-urlencode url=https://example.com \
-o shot.webp
Python:
import requests
r = requests.get(
"https://api.screenshotneo.com/v1/shot",
params={"access_key": "YOUR_API_KEY", "url": "https://example.com"},
timeout=90,
)
r.raise_for_status()
open("shot.webp", "wb").write(r.content)
Node.js:
const q = new URLSearchParams({
access_key: 'YOUR_API_KEY',
url: 'https://example.com'
});
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`Screenshot request failed: ${res.status}`);
const fs = await import('node:fs/promises');
await fs.writeFile('shot.webp', Buffer.from(await res.arrayBuffer()));
ScreenshotNeo offers 1,000 screenshots a month free with no card; paid plans start at $5 for 3,000 screenshots. Sign up for the free plan.
FAQ
Can I turn an already-developed web app into a PWA?
Usually, yes. You can add PWA capabilities to the existing app; the amount of work depends on its hosting, routes, browser support needs, and offline goals.
Do I have to rebuild it as a single-page app?
No. A PWA can be built with either a multi-page or single-page architecture.
Will every feature work without a network?
No. Offline support depends on what your app deliberately caches or implements for disconnected use. Installation does not make server-dependent features available offline.
Is publishing in an app store required?
No. Web distribution is an option; packaging for a store is separate and optional.
What if I am changing the domain of an already-installed PWA?
That is a separate, browser-specific origin migration problem, not the process of adding PWA features to an existing app. Chrome for Developers announced a PWA Origin Migration feature in Chrome 150 on June 3, 2026, using a two-way authorization handshake between the old and new same-site origins. See Chrome’s origin migration announcement and check current browser support before relying on it.


