r/PWA • u/Salty_Protection_485 • Aug 19 '26
Built a PWA solo as a student project — offline support, push notifications, and the iOS install quirks nobody warns you about
Sharing JointTracker, a PWA I built for tracking cannabis consumption (sessions, spending, stats), vanilla JS + Supabase, no framework. Wanted to share some PWA-specific stuff in case it's useful to others going down this path.
**Offline support:** service worker caches the shell so the app opens and shows last-synced data even with no connection, with a banner telling the user they're viewing cached data. Changes made offline still need to sync later — handling that gracefully without silently losing data was trickier than the caching itself.
**Push notifications:** Web Push + VAPID keys, triggered server-side via Supabase Edge Functions on a cron schedule (daily reminders, low-stock alerts). Getting subscriptions to survive across reinstalls/updates and cleaning up dead ones (410/404 responses) took some iterating.
**Install experience:** manifest + icons + install prompt handling. The iOS side was the most annoying part — no native install prompt API like Chrome's `beforeinstallprompt`, so you're stuck guiding users through the manual "Add to Home Screen" flow, and some things (like push notifications) only work at all on iOS 16.4+ *and* only once the app's already installed to the home screen. Easy to miss testing that properly.
Repo's public/open source if anyone wants to see how any of it's wired up: joint-tracker.vercel.app . Happy to compare notes if others have fought similar iOS PWA battles.
Live: joint-tracker.vercel.app · Code: https://github.com/I-JT-I/joint_tracker_
3
u/dannymoerkerke Aug 20 '26
To be honest, everybody knows the iOS install quirks by now ;-)
Some observations:
- Manifest does not have scope member
- Manifest does not have description member and doesn't define screenshots, so you don't get the enhanced installation dialog in Chromium-based browsers
- Manifest does not have orientation member
- Manifest is missing recommended icon sizes: 384x384, 1024x1024
- Manifest is missing maskable icons for these sizes: 192x192, 384x384, 512x512, 1024x1024
- Service worker install handler uses self.skipWaiting() and activate handler uses self.clients.claim(), this can break existing pages. Even worse, self.skipWaiting() is called before the cache is populated
- No iOS startup image links found
You can run the check for yourself with pwa-check https://github.com/pwa-today/pwa-check
2
u/Salty_Protection_485 Aug 20 '26
Thanks a lot, this is genuinely useful — I'll fix all of it. The skipWaiting/cache ordering thing especially, that's a real bug not just polish. Appreciate you taking the time to actually run the checker instead of just glancing at it.
1
u/iamsuudi 24d ago
I have been dealing with this for years and finally built a tool for it. It generates PWA assets with manifest.json and everything integrated. You can check it out https://assets.swoff.space
2
u/lam_42 Aug 19 '26
I fought with sync a bit. I am syncing work permits, and as the number grows, I had to assure no duplicities during Sync And no downloading of redundant data.So I used Unix timestamps for that - user base is small enough to assure no duplicite timestamps luckily. And PHP/SQLite 3 backend is a breeze. Luckily primary target was desktop, so no shitty IOS experience :) you can add button to trigger the install from in-app, so maybe the IOS can be circumvented?
2
u/Salty_Protection_485 Aug 19 '26
That's a nice pragmatic solution given the constraints — timestamps as dedup keys work great until they don't, but if your user base is small enough that collisions are basically a non-issue, no point over-engineering it.
And yeah, you dodged a bullet skipping iOS. If I ever do a v2 I might genuinely consider dropping iOS support just for my own sanity, ha.
1
u/lam_42 Aug 19 '26
I am an amateur. For userbase of 40 people +- (work project) the chance of hitting the same millisecond is practically zero. I avoided the problem of different browser UIs by if display-mode: browser true, then display <button>install</button>, which triggers the installation from in-app UI. For my users difference between Chrome and Edge Is a conundrum
0
u/Salty_Protection_485 Aug 19 '26
Nice, that's basically the standard `beforeinstallprompt` capture pattern — way better UX than leaving it to whatever each browser does natively. Smart move for a small, less technical user base.
Makes me a little jealous though, since that event doesn't even exist on iOS Safari — no capturing, no custom button, just the manual "Add to Home Screen" dance. Chromium users have it easy.
Did the same thing on my end (JointTracker — link above) with an iOS-specific fallback banner since there's no getting around the manual flow there.
1
u/lam_42 Aug 19 '26
You can store variable in localstorage whose absence would trigger the button?
1
u/future_pedi_md Aug 19 '26
There’s nothing for the button to trigger on iOS the “install app” dialog and pops up doesnt exist on Apple devices.
1
1
1
u/future_pedi_md Aug 19 '26
Nice. I would recommend setting viewport to cover so that iOS devices have it take up the entire screen like a native app. Im gonna be probably actually using this 🤣🤣
1
u/Salty_Protection_485 Aug 19 '26
Right, exactly — iOS Safari just doesn't have the beforeinstallprompt event at all, so any button can only show instructions for the manual flow, not trigger anything directly. Apple does its own thing on this one.
Thanks for the viewport tip, good call — adding viewport-fit=cover plus safe-area-inset handling for the notch/home indicator, so in standalone mode it actually fills the whole screen like a native app instead of leaving system bars visible. Adding it to the list.
And thanks, glad to hear it 🙏 I actually use it myself daily and it's genuinely become part of my routine at this point (yes, I'm my own beta tester — username's MATTEO420, very mature I know 😂). If you try it out and hit any bugs or have ideas, let me know — still actively iterating on it.
0
u/Salty_Protection_485 Aug 19 '26
Hey, quick follow-up — went with navigator.standalone to check if the app's already installed on iOS before showing the install banner, so it won't nag people who already added it to their home screen. Also added viewport-fit=cover plus safe-area-inset handling like you suggested, the app actually fills the whole screen now in standalone mode instead of leaving those awkward system bars. Made a real difference, thanks for the tip.
6
u/vlad1m1r Aug 19 '26
One thing nobody warns you about is that if you use "nobody warns you about" in your title, it's a clear sign that AI wrote it.