A funny thing happened on the way to the app stores: the browser quietly learned to be an operating system.

In 2025, a Progressive Web App can be installed, run offline, send push notifications (even on iOS), capture links and files, badge its icon, and sit in your task switcher like any other app—while still deploying as a folder of static files behind a CDN.

This is an opinionated field guide to PWAs right now—what's real, what still bites, and where it makes sense to replace (or complement) native and heavy SPA frameworks. I'll tie this to a pragmatic HTMX-style architecture—HTML over the wire, minimal JavaScript—and give you concrete patterns, checklists, and code.

---

What Exactly Is a PWA in 2025?

A PWA is just a website with three extra ingredients:

  1. HTTPS (mandatory for service workers).
  2. A Web App Manifest (manifest.webmanifest) describing name, icons, start URL, and window mode (e.g. display: "standalone").
  3. A Service Worker that gives you offline caching, request interception, background tasks, and push plumbing.

When those are present and the runtime deems the app "installable," the browser offers an install surface and the OS treats it like an app window.

On Windows, Microsoft actively embraces PWAs—native window chrome, store submission, widgets, app actions. On Android, you can wrap a PWA as a Play Store app via Trusted Web Activity (TWA). On iOS, PWAs install from Safari and—after last year's DMA kerfuffles—remain supported, with Apple affirming Home-Screen web apps will continue to work.

A good PWA also uses capability APIs judiciously: Web Push and Notifications, File System Access (Chromium), Badging, Protocol and File Handlers, Share/Share Target, and (where available) Background/Periodic Sync. Treat these as progressive enhancements; support varies.

Related reading:

---

Where PWAs Shine (and Where They Don't)

Strengths You Can Bank On

  • Installability without gatekeepers. Users can add to Home Screen/desktop instantly; Windows and Android stores are optional but available—especially easy via PWABuilder and TWA.
  • Offline-first UX with instant loads. Service workers enable cache-then-network strategies (stale-while-revalidate etc.) for documents, APIs, and assets. The user benefit shows up in Core Web Vitals.
  • Push on every major platform. iOS added Web Push for installed web apps in iOS 16.4; desktop and Android had it for years. There are caveats (see below), but the door is open.
  • OS integration keeps growing. App badges, shortcuts, widgets, link capture and protocol handlers now make PWAs feel at home, notably on Windows and macOS.
  • Proven business impact. Canonical case studies like Twitter Lite, Starbucks, and Uber showed engagement gains and lower friction, especially on low-end devices and constrained networks.

Limitations You Must Design Around

  • Safari gaps & policies. Background Sync isn't supported on Safari; some APIs are Chromium-only. iOS Web Push requires installation and user gestures. Plan fallbacks and reduce push dependence for critical flows.
  • Storage is quota-managed and evictable. Treat browser storage as a cache. IndexedDB/Cache API can be large on desktop, but mobile quotas and eviction rules vary; request persistent storage only if it's truly essential.
  • No iOS App Store listing as a PWA. You can ship a wrapper, but App Store policy and UX are your problem; discovery relies on web/SEO and smart in-app install prompts. (Windows and Play Store are friendlier.)
  • Push and notification fatigue. Chrome now auto-revokes noisy, ignored notifications; treat push like a surgical instrument, not a fire hose.

---

The PWA + HTMX Pattern: HTML Over the Wire, Service Worker in the Middle

That pairing is delicious:

  • HTMX handles interactivity by swapping server-rendered fragments (hx-get, hx-swap) instead of booting a client SPA runtime.
  • The service worker intercepts those fragment requests, serving cached responses instantly when offline, refreshing in the background, and updating the cache opportunistically.
  • Result: you keep the simplicity, SEO, and accessibility of server-rendered HTML, but with app-like speed, resilience, and installability.

That's the thesis we outlined in our HTMX 2025 write-up: push complexity to the edges, ship fewer bytes of framework, and let the browser do its job. A PWA service worker is the ideal "edge."

---

Architecture You Can Ship This Quarter

Think in layers:

  1. Origin contract: predictable URLs for documents and fragments (content-addressed or versioned).
  2. Caching policy: pre-cache app shell; runtime cache HTML, JSON, and media with tailored strategies.
  3. Revalidation: ETag/Last-Modified and SWR (stale-while-revalidate) to keep content fresh without blocking paint.
  4. Resilience: idempotent writes, sync queues, and "draft until synced" UI.
  5. Progressive capabilities: push, badging, file/URL handlers, shortcuts, only where supported.

Minimal Manifest

{
  "name": "Acme Reader",
  "short_name": "Acme",
  "start_url": "/?source=pwa",
  "display": "standalone",
  "background_color": "#0b0b0c",
  "theme_color": "#0b0b0c",
  "icons": [
    {"src": "/icons/icon-192.png", "sizes": "192x192", "type": "image/png"},
    {"src": "/icons/icon-512.png", "sizes": "512x512", "type": "image/png"}
  ],
  "shortcuts": [
    {"name": "My Queue", "url": "/queue"},
    {"name": "Downloads", "url": "/downloads"}
  ]
}

Use display: "standalone" for an app-like window and add shortcuts for quick actions.

Service Worker: Workbox in Three Routes

Workbox makes production caching strategies explicit and testable:

// sw.js
import {precacheAndRoute} from 'workbox-precaching';
import {registerRoute} from 'workbox-routing';
import {StaleWhileRevalidate, NetworkFirst, CacheFirst} from 'workbox-strategies';

precacheAndRoute(self.__WB_MANIFEST || []); // build-time list

// 1) HTML documents: fast first paint, then refresh quietly. registerRoute( ({request}) => request.destination === 'document', new NetworkFirst({ cacheName: 'html', networkTimeoutSeconds: 3 }) );

// 2) HTMX/JSON fragments: stale-while-revalidate keeps UI snappy. registerRoute( ({request, url}) => request.destination === '' && (url.pathname.startsWith('/fragment/') || url.pathname.endsWith('.json')), new StaleWhileRevalidate({ cacheName: 'data' }) );

// 3) Static assets: cache-first, expire aggressively. registerRoute( ({request}) => ['style','script','font','image'].includes(request.destination), new CacheFirst({ cacheName: 'assets' }) );

These strategies ("network-first for documents, SWR for data, cache-first for assets") map to how users perceive freshness vs. speed.

Background Refresh (When Supported)

Chromium's Periodic Background Sync lets your SW wake up occasionally to refresh caches so content is fresh on launch. Safari/Firefox don't support it—treat it as an enhancement.

// In main thread
if ('serviceWorker' in navigator && 'periodicSync' in (await navigator.serviceWorker.ready)) {
  try {
    await navigator.serviceWorker.ready.then(reg =>
      reg.periodicSync.register('refresh-feed', { minInterval: 12 * 60 * 60 * 1000 })
    );
  } catch {}
}

Push + Badging (The Humane Version)

Request permission from a user gesture; badge the app only when there's genuine value.

// main.js
async function enableUpdates() {
  const perm = await Notification.requestPermission();
  if (perm !== 'granted') return;
  const reg = await navigator.serviceWorker.ready;
  const sub = await reg.pushManager.subscribe({ userVisibleOnly: true, applicationServerKey: VAPID_KEY });
  await fetch('/api/push/subscribe', { method: 'POST', body: JSON.stringify(sub) });
  if (navigator.setAppBadge) await navigator.setAppBadge(1);
}

Badging gives you a subtle, OS-native affordance without spamming toasts.

---

Where to Deploy PWAs

  • The open web (always).
  • Microsoft Store: one-click packaging via PWABuilder; store presence improves discovery on Windows.
  • Google Play: wrap with TWA (Bubblewrap CLI) for full-screen Chrome inside an APK.

---

Quality Bar: What "Good" Looks Like

  • Installable: Valid manifest + SW; real icons; display: standalone; start_url stable.
  • Vitals: LCP, INP, CLS in green; measure via CrUX, DevTools, and real-user monitoring.
  • Resilience: offline UX for core journeys; lossless write paths (enqueue until online).
  • Resource hygiene: versioned URLs for assets; SW cache cleanup on activate; no precaching of bulky, volatile media.

Lighthouse remains useful for perf and best practices—PWA-specific audits have evolved in DevTools, so rely on installability checks and your own SW tests.

---

The HTMX + PWA "Reader" Blueprint

A lot of teams have a "content-heavy, must-work-offline, must-be-fast" requirement (knowledge bases, legal libraries, courseware, docs). Here's a concrete, QA-ready blueprint.

Goals

  • Instant loads after first visit; core sections readable offline.
  • Seamless navigation via HTMX; no client router.
  • Sync queue for notes/highlights when offline.
  • Gentle re-engagement via badging/push on real updates.
  • Play Store + Microsoft Store presence (optional).

Server

Routes:
  • /doc/:id → full HTML document (ETagged; long Cache-Control + must-revalidate)
  • /fragment/sidebar/:docId → HTML partial (ETagged)
  • /api/annotations (POST/GET) → idempotent writes; user-scoped; accepts out-of-order timestamps
HTMX: Links/buttons use hx-get to fetch partials; hx-push-url for history. Content versioning: Every build emits a content version X-Content-Rev header; emits SRI-versioned assets.

Client

Manifest with shortcuts to "My Library," "Downloads." Icons at 192/512; screenshots for nicer install UI. Service Worker (Workbox):
  • Precache shell: CSS/JS, chrome, empty states.
  • Documents: NetworkFirst({ networkTimeoutSeconds: 3 }) to avoid long tail.
  • Fragments & JSON: StaleWhileRevalidate with cache-key by URL + X-User for personalized panes.
  • Assets: CacheFirst with expiration and revisioned URLs.
  • On activate, delete old caches that don't match current X-Content-Rev.
Sync:
  • In the page, writes are POST /api/annotations. If offline, push to indexedDB.outbox.
  • On online or on SW sync (one-off Background Sync in Chromium), flush the outbox; dedupe by deterministic client ID. Safari gets manual "Sync now" affordance.
Re-engagement:
  • After installation, offer opt-in push ("Notify me when this doc updates"). Server emits targeted Web Push with doc checksum; client compares; if changed, set navigator.setAppBadge(1).

Installation & Stores

  • Windows Store: package via PWABuilder; add app actions for "Search," "Open last doc."
  • Play Store: wrap with TWA (Bubblewrap). Enforce asset links; verify address bar hides.

QA Checklist

  1. Installability: DevTools Application panel → Manifest passes; app launches as standalone window.
  2. Offline: with network off, launch PWA; /doc/:id renders; links swap via HTMX from cache; error messaging is humane when content truly missing.
  3. Freshness: throttle to 3G; confirm SWR pulls new fragment in background and updates seamlessly.
  4. Write resilience: kill network mid-edit; annotations land in IndexedDB outbox; sync later is idempotent.
  5. Push: opt-in path requires user gesture; unsub/re-sub stable; badging increments and clears on read.
  6. Storage: navigator.storage.estimate() stays well below quota; caches rotate on X-Content-Rev.

This pattern is intentionally conservative—no heroic APIs required to get a great experience. Where supported, Periodic Background Sync is a cherry on top, not a dependency.

---

Practical "Don'ts" That Save Months

  • Don't precache the world. Precache the shell and critical paths; let runtime caching learn the rest. Big media belongs in streaming or lazy caches.
  • Don't trust browser storage with canonical state. Treat it as a cache; your system of record is server-side. Eviction happens.
  • Don't spam push. Chrome literally disables ignored notifications now. Make every message a favor, not an interruption.
  • Don't assume background tasks exist. Safari users are people too; give them a "Refresh" button and design flows that work without scheduled jobs.

---

When to Choose a PWA Over Native or a Heavy SPA

Choose PWA for:
  • Content-centric apps (news, docs, reference, learning, catalogs) where offline and snappy navigation matter more than deep device APIs.
  • Transactional apps with simple peripherals (forms, checkouts, scheduling) where WebAuthn/passkeys, Share, and Payments cover the needs.
  • Field and internal tools where Windows desktops and Android devices dominate; add Store presence for distribution convenience.
Maybe not:
  • Deeply sensor-heavy apps, low-latency Bluetooth or background geofencing, or apps that must execute many minutes of unbounded background work on iOS—today's web still boxes you in there.

---

A Note on "Build a PWA in a Weekend" Pieces

You've probably seen blog posts claiming you can ship a PWA before Monday. You can—installation and offline shell, yes. But a great PWA takes the same rigor as any product: measuring Core Web Vitals, designing for partial connectivity, planning data ownership, and building a respectful push strategy. Use those weekend guides for scaffolding, not for the bar you aim to clear.

---

The 2025 Reality Check

  • PWAs are first-class on Windows and Android (store packaging, OS surfaces).
  • On iOS they are usable and useful, with push and install—but still bounded by WebKit's background and API policies. Apple's EU stance last year underscored that the platform is policy-sensitive; don't over-invest in features that assume daemon-like background behavior.
  • The capability surface keeps growing (badging, file/URL handlers, widgets, App Actions), especially in Chromium. Use them as optional enhancers.

And yes, this all pairs beautifully with an HTMX mindset: the less framework you ship, the more the network and the browser can help you. That's not nostalgia; it's performance engineering.

---

References & Further Reading

Core Specs & Platform Primitives

iOS & Push

Packaging & Distribution

Capabilities

Storage & Quotas

Performance & Quality

Notification UX

Case Studies

  • Twitter Lite: PWA architecture & lessons
  • Flipkart Lite PWA case study

Related Gothar Articles

---

Closing

If your team's mental model of the web is "documents with some JS sprinkled on top," you're underestimating the platform. If your mental model is "JS runtime that happens to live in a browser tab," you're over-complicating it.

In 2025, a PWA is an application you can view source on. It is installable, resilient, and respectful of the user's device and attention. It plays well with server-rendered HTML (HTMX), and it inherits global distribution and SEO from the open web. Use the stores where they help; ignore them where they don't.

The browser became your cross-platform runtime. You didn't have to ask permission.

Now the task is to design like that freedom matters.