A 5-clinic pediatric practice across Mumbai's western suburbs — 28 doctors, ~6,400 monthly bookings, parents who book at 11 pm with one hand while a toddler sleeps on the other shoulder — came to us in July 2025 with a clear ask: build us a native iOS + Android app. We shipped a Next.js PWA in 7 weeks for a third of the native quote. Twelve weeks post-launch, install-to-booking conversion was 62%, and the practice cancelled their planned native build. This post is the cost math, the 3 PWA features that actually won parents over, and the one capability where we would still pick native.
TL;DR (60 Words)
We built a Next.js 14 PWA with service-worker offline caching, web push notifications, and an "Add to Home Screen" prompt. No app store. 12-week metrics: 62% install-to-booking conversion, 31% no-show drop, 11.4 day average from install to repeat booking. The one capability where native still wins: medical-record camera scan with on-device OCR.
The Client (Specific Details)
Why This Matters Now
Web Push Notifications now work on iOS Safari (16.4+) for any installed PWA — MDN's PWA notifications guide documents the cross-platform support. That single change in iOS 16.4 (April 2023) flipped the build-vs-PWA question for healthcare booking apps. Before iOS push, you needed a native app to send appointment reminders. Now you do not. For an Indian pediatric practice serving parents across iOS and Android, the PWA path saves on the up-front build and on yearly store maintenance.
The Cost Math (PWA vs Native iOS + Android)
| Cost line | PWA (what we shipped) | Native iOS + Android (the alt quote) |
|---|---|---|
| Maintenance (annual) | One stack | Two stacks, OS upgrades |
| Time-to-launch | 7 weeks | 14–18 weeks (incl. store reviews) |
| Install friction (parent's POV) | Add to Home Screen, 0 reviews | App Store search, 60+ MB download |
The cost gap closes if you have a 50,000+ install base needing app-store discoverability or you need a capability the web cannot give you (continuous Bluetooth, background audio, deep iOS integrations). For a pediatric practice with a known patient base of 23,000 families, the discoverability argument was moot. Parents come to the practice's website. They install from there.
The 3 PWA Features That Won Parents Over
The Architecture
We followed Next.js's official PWA guide for the manifest + service worker plumbing. Web push is self-hosted (no Firebase) using the standard VAPID keys + node web-push library. The Practice Management System (PMS) is the source of truth for clinical data; our PWA writes appointments through a thin shim and reads back authoritative state every 90 seconds.
The Service Worker (The 80-Line File That Earns Its Keep)
// /public/sw.js - the Workbox-generated worker we ship
import { precacheAndRoute } from 'workbox-precaching';
import { registerRoute } from 'workbox-routing';
import { NetworkFirst, CacheFirst } from 'workbox-strategies';
import { ExpirationPlugin } from 'workbox-expiration';
precacheAndRoute(self.__WB_MANIFEST);
// Vaccine card data: cache aggressively, network-first only when fresh needed
registerRoute(
({ url }) => url.pathname.startsWith('/api/vaccine-card/'),
new NetworkFirst({
cacheName: 'vaccine-card',
networkTimeoutSeconds: 3,
plugins: [
new ExpirationPlugin({ maxEntries: 50, maxAgeSeconds: 30 * 24 * 60 * 60 }),
],
})
);
// Doctor photos and clinic images: long-lived, cache-first
registerRoute(
({ request }) => request.destination === 'image',
new CacheFirst({
cacheName: 'images',
plugins: [
new ExpirationPlugin({ maxEntries: 200, maxAgeSeconds: 90 * 24 * 60 * 60 }),
],
})
);
// Web push handler
self.addEventListener('push', (event) => {
const data = event.data.json();
event.waitUntil(
self.registration.showNotification(data.title, {
body: data.body,
icon: '/icons/icon-192.png',
badge: '/icons/badge-72.png',
tag: `appt-${data.appointmentId}`,
actions: [
{ action: 'reschedule', title: 'Reschedule' },
{ action: 'dismiss', title: 'OK' },
],
data: { appointmentId: data.appointmentId, url: data.url },
})
);
});
// Notification click handler with action routing
self.addEventListener('notificationclick', (event) => {
event.notification.close();
const target = event.action === 'reschedule'
? `/appointment/${event.notification.data.appointmentId}/reschedule`
: event.notification.data.url;
event.waitUntil(clients.openWindow(target));
}); Three things to notice. The vaccine card uses NetworkFirst with a 3-second timeout — the parent gets the latest data when network is good, but a slightly stale copy when the network is bad. Images are CacheFirst (cosmetic, fine to be stale). The push handler honours iOS 16.4's strict requirement that showNotification is called synchronously inside the push event with no async work in between (or iOS punishes the subscription).
The 7-Week Build Plan
What Happened in 12 Weeks Post-Launch
The Pre-Launch Checklist (We Refuse to Cut Over Without This)
- Web push verified on iOS Safari 16.4, 17.x, 18.x and Android Chrome 119+
- Add-to-Home-Screen tested on iOS Safari, Chrome Android, Samsung Internet
- Manifest icons in all required sizes (5 for iOS, 3 for Android, plus maskable)
- Service worker tested on cold network (offline, 2G throttle, intermittent)
- VAPID keys stored in env vars, rotated quarterly
- Push notification 24h/2h/30min schedules tested with sample bookings
- PMS sync delta logged with a 30-day retention and a daily reconciliation report
- Vaccine card tested for offline read after fresh login on a real iPhone in airplane mode
- One-tap reschedule link tested with iOS notification action API
- Roll-back plan: previous service worker version cached for 7-day fallback
When PWA is the Wrong Choice (We Would Pick Native)
Common Mistakes (Each One Hurts)
Symptom: "iOS users do not get push notifications." Cause: forgot the iOS 16.4 requirement that the user must add the PWA to home screen first. Push subscription only works after install. Fix: design the install flow to be the first push-related interaction.
Symptom: "Service worker caches the wrong version after deploy." Cause: forgot to bump the service worker version or used aggressive cache-first on HTML. Fix: NetworkFirst on HTML, version-bump the SW on every deploy, ship a "new version available, refresh" toast.
Symptom: "Android Chrome installs as a Webview, not a PWA." Cause: missing or invalid manifest fields (most often start_url or display). Fix: validate the manifest with the Lighthouse PWA audit before shipping.
Symptom: "Push delivery rate is 60% on Android." Cause: Android battery optimisation kills the push service in many OEM ROMs (Xiaomi, Oppo, Realme are the worst). Fix: prompt users on first install to disable battery optimisation for the PWA. Document the per-OEM steps.
Symptom: "Parents say 'where do I download the app?'" Cause: PWA is unfamiliar; parents look in the App Store. Fix: clear in-page banner, WhatsApp install instructions to existing patients, "How to install" video at 90 seconds.
A Detail That Saved Us On Day 9
On day 9 of operation, push delivery to iPhones dropped from 94% to 71% overnight. We thought we had a VAPID key issue. The actual cause: iOS 18.0.1 had shipped that day with a stricter requirement that the service worker call showNotification within 5 seconds of receiving the push event. Our handler was doing a database write before the notification call, taking ~6 seconds on a slow network. Fix was a one-line reorder. Recovery to 95% push delivery within 90 minutes. We now bake "showNotification first, side-effects after" into our PWA review checklist. The lesson: read iOS release notes the day they ship, especially the WebKit ones.
FAQ
Does iOS Safari really support web push?
Yes, since iOS 16.4 (April 2023) for installed PWAs only. The user must add the site to their home screen first. After that, push works on iOS Safari and Chrome iOS (which runs on WebKit anyway).
What is the install-acceptance rate for PWA prompts?
In our experience: 18% if you prompt on first visit (low intent), 71% if you prompt after the second successful interaction (high intent + clear value demonstrated). The PWA prompt is one of those features where timing is everything.
Why no Firebase for push notifications?
VAPID + node web-push library handles both iOS and Android natively. We avoid Firebase to keep the dependency surface small and avoid Google's data-collection defaults for a healthcare client.
How do you handle the 11-year-old PMS?
A thin Next.js API shim that wraps the PMS's SOAP endpoints. We cache doctor availability for 90 seconds. We write through synchronously for bookings and read authoritative state every 90 seconds. The shim is a single Node service that we can replace independently when the practice upgrades the PMS.
What about parent identity verification?
OTP-based phone login with optional fingerprint sign-in on the device (Web Authentication API). For sensitive vaccine-card writes we require OTP re-auth with a 15-minute session.
Can the PWA accept payments?
Yes. We integrated Razorpay for consultation pre-payment. The web checkout works in the PWA exactly as it does on the website. We did not need Apple Pay or Google Pay's app-specific flows.
What is on the v2 roadmap?
Phase 2 is teleconsultation video (we will use Daily.co's React SDK) and a parent-to-doctor secure messaging thread. Phase 3 is the on-device vaccine-card OCR — we will likely go native at that point or wait for browser camera + ML support to mature.
What was the team for this build?
Two engineers (one full-stack lead, one frontend specialising in PWA), our designer Khushi at 0.5 FTE for the parent-facing flows, and our QA lead Manvi at 0.3 FTE for cross-device push testing. Our CTO Hrishikesh at 0.2 FTE for architecture review.
Want a Healthcare Booking PWA?
Need a Healthcare Booking PWA?
We build PWAs for 3-15 clinic Indian healthcare practices. Typical engagement: 6-9 weeks, fixed-price. We integrate with your existing PMS via REST or SOAP shim. No app-store dependency, no native maintenance burden. First call is with the engineer who would lead your build.
Book a 30-min CallRelated reading: our 9-clinic diagnostics chain booking portal, the TalkDrill mobile case study, and our mobile development service.
For more on cross-platform mobile decisions, the team also writes regularly about real-time AI on our edtech product a client platform. Email contact@softechinfra.com to receive the PWA-vs-native decision spreadsheet template before the call.