Progressive Web Apps in 2025: The Complete Implementation Guide
Build production-ready PWAs with modern APIs. Covers service workers, Web Push, offline support, app manifest, install prompts, background sync, and...
Why PWAs in 2025?
Progressive Web Apps have matured significantly. With iOS finally improving PWA support and new APIs like Background Sync, Web Push (on iOS since 16.4), and the File System Access API, PWAs can now replace native apps for most use cases.
A well-structured configuration file is the foundation of reproducible infrastructure.
Benefits:
- One codebase for web, Android, iOS, and desktop
- No app store approval process
- Instant updates without user action
- Smaller footprint than native apps
- Works offline with service workers
- Installable on home screen
The Manifest
The web app manifest defines how your PWA appears when installed:
// public/manifest.json
{
"name": "TechSaaS Platform",
"short_name": "TechSaaS",
"description": "Self-hosted infrastructure management",
"start_url": "/dashboard",
"display": "standalone",
"background_color": "#0f172a",
"theme_color": "#3b82f6",
"orientation": "any",
"scope": "/",
"icons": [
{
"src": "/icons/icon-192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "/icons/icon-512.png",
"sizes": "512x512",
"type": "image/png"
},
{
"src": "/icons/icon-maskable-512.png",
"sizes": "512x512",
"type": "image/png",
"purpose": "maskable"
}
],
"screenshots": [
{
"src": "/screenshots/desktop.png",
"sizes": "1280x720",
"type": "image/png",
"form_factor": "wide"
},
{
"src": "/screenshots/mobile.png",
"sizes": "750x1334",
"type": "image/png",
"form_factor": "narrow"
}
]
}
Get more insights on Tutorials
Join 2,000+ engineers who get our weekly deep-dives. No spam, unsubscribe anytime.
Link it in your HTML:
<link rel="manifest" href="/manifest.json">
<meta name="theme-color" content="#3b82f6">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
Service Worker
The service worker handles caching, offline support, and background tasks:
// public/sw.js
const CACHE_NAME = "techsaas-v1";
const STATIC_ASSETS = [
"/",
"/dashboard",
"/offline",
"/icons/icon-192.png",
"/icons/icon-512.png",
];
// Install — cache static assets
self.addEventListener("install", (event) => {
event.waitUntil(
caches.open(CACHE_NAME).then((cache) => {
return cache.addAll(STATIC_ASSETS);
})
);
self.skipWaiting();
});
// Activate — clean up old caches
self.addEventListener("activate", (event) => {
event.waitUntil(
caches.keys().then((keys) => {
return Promise.all(
keys
.filter((key) => key !== CACHE_NAME)
.map((key) => caches.delete(key))
);
})
);
self.clients.claim();
});
// Fetch — network first, cache fallback
self.addEventListener("fetch", (event) => {
// Skip non-GET requests
if (event.request.method !== "GET") return;
// API requests — network only
if (event.request.url.includes("/api/")) {
event.respondWith(
fetch(event.request).catch(() => {
return new Response(JSON.stringify({ error: "Offline" }), {
headers: { "Content-Type": "application/json" },
status: 503,
});
})
);
return;
}
// Static assets — cache first
if (event.request.url.match(/\.(js|css|png|jpg|svg|woff2)$/)) {
event.respondWith(
caches.match(event.request).then((cached) => {
return cached || fetch(event.request).then((response) => {
const clone = response.clone();
caches.open(CACHE_NAME).then((cache) => cache.put(event.request, clone));
return response;
});
})
);
return;
}
// Pages — network first, cache fallback
event.respondWith(
fetch(event.request)
.then((response) => {
const clone = response.clone();
caches.open(CACHE_NAME).then((cache) => cache.put(event.request, clone));
return response;
})
.catch(() => {
return caches.match(event.request).then((cached) => {
return cached || caches.match("/offline");
});
})
);
});
Registration
// src/lib/register-sw.ts
export async function registerServiceWorker() {
if ("serviceWorker" in navigator) {
try {
const registration = await navigator.serviceWorker.register("/sw.js", {
scope: "/",
});
// Check for updates periodically
setInterval(() => {
registration.update();
}, 60 * 60 * 1000); // Every hour
console.log("SW registered:", registration.scope);
} catch (error) {
console.error("SW registration failed:", error);
}
}
}
Docker Compose brings up your entire stack with a single command.
Web Push Notifications
// Request permission and subscribe
async function subscribeToPush() {
const permission = await Notification.requestPermission();
if (permission !== "granted") return;
const registration = await navigator.serviceWorker.ready;
const subscription = await registration.pushManager.subscribe({
userVisibleOnly: true,
applicationServerKey: urlBase64ToUint8Array(VAPID_PUBLIC_KEY),
});
// Send subscription to your server
await fetch("/api/push/subscribe", {
method: "POST",
body: JSON.stringify(subscription),
headers: { "Content-Type": "application/json" },
});
}
Handle push events in the service worker:
// In sw.js
self.addEventListener("push", (event) => {
const data = event.data?.json() || {};
event.waitUntil(
self.registration.showNotification(data.title || "Notification", {
body: data.body,
icon: "/icons/icon-192.png",
badge: "/icons/badge-72.png",
data: { url: data.url || "/" },
actions: data.actions || [],
})
);
});
self.addEventListener("notificationclick", (event) => {
event.notification.close();
const url = event.notification.data.url;
event.waitUntil(clients.openWindow(url));
});
Install Prompt
"use client";
import { useState, useEffect } from "react";
export function InstallPrompt() {
const [deferredPrompt, setDeferredPrompt] = useState<any>(null);
const [showInstall, setShowInstall] = useState(false);
useEffect(() => {
const handler = (e: Event) => {
e.preventDefault();
setDeferredPrompt(e);
setShowInstall(true);
};
window.addEventListener("beforeinstallprompt", handler);
return () => window.removeEventListener("beforeinstallprompt", handler);
}, []);
const handleInstall = async () => {
if (!deferredPrompt) return;
deferredPrompt.prompt();
const { outcome } = await deferredPrompt.userChoice;
console.log("Install outcome:", outcome);
setShowInstall(false);
setDeferredPrompt(null);
};
if (!showInstall) return null;
return (
<div className="install-banner">
<p>Install TechSaaS for a better experience</p>
<button
<button => setShowInstall(false)}>Dismiss</button>
</div>
);
}
Background Sync
Free Resource
Free Cloud Architecture Checklist
A 47-point checklist covering security, scalability, cost optimization, and disaster recovery for production cloud environments.
Queue actions when offline and sync when back online:
// In your app code
async function saveData(data) {
try {
await fetch("/api/save", {
method: "POST",
body: JSON.stringify(data),
});
} catch (error) {
// Queue for background sync
const registration = await navigator.serviceWorker.ready;
await registration.sync.register("sync-data");
// Store data in IndexedDB for the service worker to use
const db = await openDB("sync-queue");
await db.add("pending", data);
}
}
// In sw.js
self.addEventListener("sync", (event) => {
if (event.tag === "sync-data") {
event.waitUntil(syncPendingData());
}
});
async function syncPendingData() {
const db = await openDB("sync-queue");
const pending = await db.getAll("pending");
for (const item of pending) {
await fetch("/api/save", {
method: "POST",
body: JSON.stringify(item),
});
await db.delete("pending", item.id);
}
}
PWA Checklist
- Web app manifest with icons (192px and 512px)
- Service worker with offline support
- HTTPS everywhere
- Responsive design
- Fast loading (LCP under 2.5s)
- Works without JavaScript (graceful degradation)
- Meta theme-color tag
- Apple touch icon
- Offline fallback page
Docker Compose defines your entire application stack in a single YAML file.
PWA vs Native in 2025
| Capability | PWA | Native |
|---|---|---|
| Push Notifications | Yes (including iOS) | Yes |
| Offline Support | Yes | Yes |
| Camera Access | Yes | Yes |
| GPS/Location | Yes | Yes |
| File System | Partial (OPFS) | Full |
| Bluetooth | Yes (Web Bluetooth) | Yes |
| Background Processing | Limited | Full |
| App Store Distribution | No (direct install) | Yes |
| Auto-Updates | Yes | Manual |
| Install Size | <1MB typically | 50-200MB |
At TechSaaS, we build PWAs for clients who want cross-platform apps without the cost of maintaining separate iOS, Android, and web codebases. For most business applications, PWAs provide 95% of native functionality at a fraction of the development cost.
Need help building a progressive web app? Contact [email protected].
Related Service
Cloud Solutions
Let our experts help you build the right technology strategy for your business.
Need help with tutorials?
TechSaaS provides expert consulting and managed services for cloud infrastructure, DevOps, and AI/ML operations.
We Will Build You a Demo Site — For Free
Like it? Pay us. Do not like it? Walk away, zero complaints. You will spend way less than hiring developers or any agency.
No spam. No contracts. Just a free demo.