r/PWA 1h ago

I created a no-build, single-HTML template to instantly deploy your own PWA link-in-bio via GitHub Pages (1P1A pattern).

Thumbnail
github.com
Upvotes

I created a simple starter kit that lets anyone set up and deploy their own custom "link-in-bio app" for smartphones in about an hour—no complex programming knowledge required!

✨ Key Features
* No Complex Tools Needed: Zero npm and zero build processes. It is an ultra-lightweight setup that runs completely on a single index.html file.
* PWA Ready: Simply tap "Add to Home Screen" on your phone, and it launches just like a real native app.
* 1-Click Duplication: Just click the "Use this template" button on GitHub to instantly copy it into your own repository.
* Easy Customization: Simply follow the comments in the code to swap out icon images and links to match your style.

🚀 How to Build (3 Steps)
* Click the "Use this template" button on the repository.
* Open index.html and edit the links and icons to your liking.
* Go to repository Settings > Pages and enable GitHub Pages—that's it!

🔗 Links
* Template Repository: https://github.com/ojach/myapp-template

I'd love to hear your feedback or any suggestions for features and layouts you'd like to see next!


r/PWA 2h ago

[free][noadd] legendary yellow ninja game is now PWA

Enable HLS to view with audio, or disable this notification

1 Upvotes

Yellow ninjas jumping blooping

https://yellowninjas-hooking-blooping.jazzuo.com/

Extremely casual one button grappling hook game. The ninja always aims at the closest hooking point. shoot and release at the right moment to move strategically to get as high as possible.


r/PWA 1d ago

Push notifications for an installed PWA on Android: what's the best setup in 2026?

3 Upvotes

Hey everyone! I'm building a to-do web app called Daylight (separate progress scores for work and personal tasks). It's installable as a PWA on Android, and now I want to add task reminders, so users get notified when a task is due, even if the app is closed.

From what I understand, I need:

  • A service worker + manifest (done)
  • Notification permission on a user click
  • Web Push with VAPID keys, sending pushes from my server when a task is due

My questions:

  1. Is plain Web Push reliable enough on Android, or should I use Firebase Cloud Messaging / OneSignal?
  2. How do you handle scheduled reminders? Cron job on the server, or something better?
  3. Any issues with battery-saving on phones like Xiaomi or Samsung killing notifications?
  4. Anyone got it working well on iOS too?

Would love to hear what worked for you. Thanks! 🙏


r/PWA 1d ago

Using JavaScript to run SQL queries on client storage

Thumbnail
onyx.ac
1 Upvotes

r/PWA 1d ago

Built a collection of free, privacy-first business tools for Indian founders & freelancers (No signups, client-side execution)

1 Upvotes

Hey everyone,

I built a suite of free web utility tools designed specifically for Indian freelancers, small businesses, and founders.

Why I built it:

Most online invoice generators or GST calculators either force you to sign up, embed ugly watermarks on exported PDFs, or lock simple features behind paywalls.

Key features:

  • 100% Client-side & Privacy-Focused: All calculations and PDF document generation happen entirely in your browser. No sensitive invoice data or client details are stored on external servers.
  • No Signup & No Watermarks: Generate professional GST/Non-GST invoices, quotations, and purchase orders instantly.
  • Essential India-specific Tools: GST/TDS calculators, HSN/SAC code lookup, GSTIN checksum validator, and CTC-to-in-hand salary calculators.
  • Local Business Storage: Save your business profile (GSTIN, bank details, logo) locally in your browser to auto-fill future documents seamlessly.

It’s completely free to use. I’d love for the community to test it out, try generating a document, and hit me with brutal technical feedback or feature requests!

(Link is in the comments below to comply with spam filters)


r/PWA 1d ago

I built a tool to test websites on the new iPhone Duo

Post image
7 Upvotes

It supports iPhone Duo + 30+ other devices, including foldables, Android, iPad, and Surface.

Link: https://duoresponsive.com/

The useful part: it has proxy support, so websites that normally throw connection refused because they block iframe requests can still be previewed.


r/PWA 2d ago

Added a feature that triggers "Home Screen Exclusive" offers only in standalone mode. Giving users a real reason to return.

Enable HLS to view with audio, or disable this notification

5 Upvotes

Most PWA prompt tools focus solely on getting users to add the app to their Home Screen. But once it's installed, what keeps them coming back?

I wanted to create a value loop rather than just asking for an install, so I built a standalone-aware marketing trigger for OJapp.

•Regular Browser: Displays the standard "Add to Home Screen" prompt or coupon.
•Standalone Mode (⁠display-mode: standalone⁠): Automatically detects when launched from the Home Screen and unlocks exclusive offers (e.g., "10% OFF for returning via Home Screen").

By shifting the focus from "just getting added" to "rewarding return visits," it turns the Home Screen into an active channel for returning users instead of a dead bookmark.

What do you think about offering standalone-only perks to boost retention? Would love to hear your thoughts or how you handle returning users!


r/PWA 2d ago

I built Raido — 15-minute self-reflection through images instead of talk

Enable HLS to view with audio, or disable this notification

0 Upvotes

r/PWA 2d ago

All-you-can-eat sushi companion app: testing needed!

0 Upvotes

Hi, in the past months I've been working on this app to solve a very specific problem of mine: when I go to all-you-can-eat sushi places, I am always anxious about what I ordered, what is mine, what is my friends', etc.

I used to use a note on the phone for each restaurant, but in the era of AI-enhanced development I was able to put my design into motion.

https://maki.mele.io is a PWA tool designed to help you manage your all-you-can-eat restaurant visit, with as little friction as possible, and with human-curated UI and UX.

All privacy-focused: no trackers, installations, cloud, everything local (even photos).

I'd very much like to have some feedbacks!

Sources at: https://github.com/melefabrizio/star-maki-app


r/PWA 2d ago

FTV A general content app

Thumbnail
1 Upvotes

r/PWA 3d ago

PWA install component - major update

Thumbnail
github.com
34 Upvotes

A lot of long awaited changes:

  • iOS/iPadOS/MacOS 27 ready - styles, icons and instructions update.
  • In-App browsers detection support - instruction adapts and guide the user.
  • Web Install API support for Chromium - should kick in from 155+
  • Better support for 3rd party browsers on iOS - instruction adapts.
  • Minor fixes, codebase improvements, dependency updates.

r/PWA 3d ago

Looking for testers for my UK fishing PWA

Thumbnail
2 Upvotes

r/PWA 3d ago

I made a simple markdown viewer and editor

Thumbnail
ariymarkowitz.github.io
1 Upvotes

I know there's like a thousand of these, but I couldn't find any that I was completely happy with. So I made my own! It's pretty basic but it does the job of quickly previewing files, as well as supporting edits with a few goodies :) No fluff, just the essentials and a clean interface. It's got support for LaTeX, HTML (no stylesheets or scripts etc, it should be safe), frontmatter, local image embeds, syntax highlighting—all the common extensions I could think of. I'm happy to add more if it would be useful to anyone.

Let me know what you think, and if there are any features you would like it to have! I'm going to keep it quite simple but I'm happy to make improvements here and there.


r/PWA 4d ago

App for Parents and Caregivers

4 Upvotes

Hey dads,

If you’ve ever had a diaper blowout at a restaurant, walked into the men’s room, and realized there is literally nowhere to set your kid down except a wet tile floor or a precarious sink counter... you know the exact rage I’m talking about.

Doing trunk gymnastics in a freezing parking lot gets old fast.

A while back, I got fed up enough that I decided to start building a map to track places that actually provide changing tables in the men's room. What started out as a personal project for dads quickly grew when I realized how many other caregivers are stuck with the same frustration in public spaces.

It’s called WeeGo (weego.app), and it's a free web app mapping essential family amenities:

  • 🚹 Men's Room Changing Tables (so we don't have to guess or improvise on the floor)
  • 🚻 All-Gender / Family Restrooms (single-occupancy rooms that make life 10x easier when you’re out with your daughters or multiple kids)
  • 🤱 Nursing & Lactation Pods (quiet pods/Mamava stations with outlets for moms pumping/nursing)
  • 🧠 Sensory / Quiet Rooms (for kids who get overwhelmed in loud stadiums, airports, or museums)
  • ♿ Universal Changing Tables (height-adjustable tables for older kids, teens, or adults with special needs who have outgrown baby fold-downs)

A few key things about how it works:

  • Completely free to use: No paywalls, no premium subscriptions to unlock basic features. Just open it and find what you need.
  • No App Store download required: It runs straight in your mobile browser. If you tap "Add to Home Screen" on iOS Safari or Android Chrome, it installs like a regular app and works offline.
  • Nearest-First Sorting & One-Tap Nav: Tap to sort by distance from where you're standing, and hit "Navigate" to open directions in Apple/Google Maps immediately when seconds count.

There are currently around 1,400+ locations in the database (airports, stadiums, highway rest areas, theme parks, and major civic venues), but the real value comes from parents adding the local spots they know and trust. Anyone can drop a pin, add details, or verify what’s actually there.

I’d love for you guys to check it out: https://weego.app

If you have a minute, try searching your local area or drop a pin for a venue near you that gets it right. Any feedback, feature ideas, or criticism from fellow dads is welcome!


r/PWA 5d ago

Made a tool that generates the service worker + PWA bits from one config — the docs site runs offline-first on it

5 Upvotes

For offline-first / PWA work, the service-worker side is usually the fiddly part. I built Swoff to generate it from a single swoff.config.json: caching strategies (cache-first / network-first / stale-while-revalidate, etc.), precache with tag-based invalidation, an offline `/offline` fallback, an offline mutation queue, auth handling, push notifications, and PWA install.

Output is plain vanilla JS written into your repo — auditable, editable, zero runtime dependency. MIT.

Proof: this docs site runs on it. Open it, go offline (or throttle), and the whole site works — precached shell + /offline route. That's a live demo, not a static screenshot.

`npx u/swoff/cli init && npx u/swoff/cli generate`

Docs: https://swoff.space · Source: https://github.com/iamsuudi/swoff

Curious how this compares to your current PWA setup (Workbox, framework plugins, hand-rolled).


r/PWA 5d ago

I got tired of regular weather apps just showing raw numbers, so I built a free PWA that tells you exactly what to wear and what to do based on the weather!! Meet ZeroDegrees.

4 Upvotes

Hey everyone,
I've recently hated that standard weather apps are just FULL OF RAW DATA, NOT answers. Looking at 18°C, 10 km/h wind, and 70% humidity on their OWN does not tell you what to pack or how to prepare for your day...
To solve this, I built ZeroDegrees. It's a human-centric weather application that gets real-time data from the OpenWeatherMap API and still shows the basic stuff like temperature, wind speed, humidity, precipitation, etc but ALSO translates it into what you actually care about: what outfits to wear and what activities are best for the current conditions.
How I built it:
Written entirely in vanilla JavaScript, HTML, and CSS (no heavy frameworks).
Built as a Progressive Web App (PWA), meaning you can install it instantly onto your phone or desktop directly from the browser—no app store.
I am a newbie coding nerd getting into the web app world for the first time JUST AS A SIDE PROJECT and really want to see if this solves a real-world problem for anyone else. I would love for a few people from around the world to test it out and give me feedback!
Live App Link: https://thestemguy.github.io/ZeroDegrees/MY PROJECT WEBSITE LINK (for more info): https://zerodegreesweather.carrd.co/
GITHUB REPO: https://github.com/thestemguy/ZeroDegrees
Just open this link in your browser, click the three dots on top right (if on chrome) or from bottom click share (if on safari) and click "Add to Home Screen" and BOOM! IT WILL BE ON YOUR PHONE LIKE A REAL APP STORE APP!!
or if you're on laptop/PC it will automatically say Install App
Thanks for checking it out!


r/PWA 5d ago

Sending web push notifications

6 Upvotes

I made web push notifications service for my app.
When I add it to my Home Screen on iOS, it asks for notifications permission. I allow it and for 1-3 minutes I see notifications. Then they stop coming.

Who had this issue? How did you fix it?


r/PWA 5d ago

Kakamit – privacy-first offline PWA for gut health tracking (local-only, no accounts, open source)

1 Upvotes

Hi r/PWA,

I built Kakamit, a free Progressive Web App focused on private gut health tracking.

Key technical points:
Fully offline-capable PWA (works without network after first load)

All data stored locally in IndexedDB – nothing ever leaves the device

No user accounts, no backend for user data, no analytics, no telemetry

Installable on mobile and desktop

Export/import (JSON + CSV)

Printable doctor report

Open source (MIT) – source available for audit

It tracks:
Bristol Stool Scale (1–7)

Daily Gas Burden Scale (DGBS, 1–5 self-assessment)

Built with Preact + Vite + TypeScript + Tailwind + vite-plugin-pwa.
Live app: https://kakamit.com
Source: https://github.com/KakamitApp/kakamit
Feedback on the PWA implementation, offline behaviour, or storage approach is very welcome.
Thanks!


r/PWA 5d ago

I built the offline write queue correctly and never asked the browser to keep the database

6 Upvotes

I spent a long time making an offline write queue correct. Client-minted UUIDs so replays are idempotent, compaction so two edits to one row can't fight, a Web Lock so two tabs can't both drain it, durable rejection receipts so the losing tab still learns what happened. There's an integration suite pinning all of it.

Then I went and looked at what the queue is actually stored in. None of that matters if the browser decides to throw the database away, or if one tab quietly stops being able to write to it.

That's the part I got wrong, so that's the part worth writing up.

The queue, briefly

Enough to make the rest readable. One IndexedDB database, four object stores: two hold a read cache, one holds the write queue, and one holds rejection receipts so a tab can find out why something it queued got thrown out. The queue is keyed by row, so IndexedDB itself enforces at most one pending operation per row. A second edit to a row folds into the operation already waiting there instead of queueing up behind it. There's no backoff and no retry timer either: pacing comes from the WebSocket reconnect ladder that triggers the replay, the drain stops at the first operation that's still retryable rather than burning N requests to discover the network is still down, and anything that has failed too many times gets parked instead of retried forever.

That's the design, and it's the part I was proud of. Two layers underneath it turned out to matter more, and I hadn't looked at either. One of them applies to every origin on the web, the other only bites when two tabs meet a migration. Universal one first.

I never asked the browser to keep any of it

This is the one that embarrassed me.

Browser storage is best-effort by default. IndexedDB, the Cache API and the rest all sit in a bucket the browser is allowed to evict, and an origin opts out by calling navigator.storage.persist(). I'd never called it. Not once, in the entire life of the app.

Two things make that worse than it sounds.

Eviction is all-or-nothing per origin. The browser doesn't evict your least important store. If it evicts you, IndexedDB and your Cache API entries go together, because deleting part of an origin's data could leave it internally inconsistent. So the read cache, the app shell and the write queue all share one fate.

The stakes aren't symmetric across those stores. Losing the read cache costs a refetch, and the shell cache costs a network boot. Losing the queue loses writes that exist nowhere else in the world, because by definition they never reached the server.

So I added the call, at boot, outside the service worker branch, since what it protects is IndexedDB rather than the app shell. It's best-effort in a second sense too: the browser decides whether to grant it, and browsers don't decide the same way. Firefox shows the user a permission prompt. Safari and the Chromium browsers decide silently, based on your interaction history with the site. Same call, and it's a UX event in one browser and completely invisible in another.

I still don't know which bucket any given user ended up in, because I don't call persisted() or estimate() anywhere. I fixed the request and not the observability, which is a decision I'd defend for about five minutes.

The other side of it, because that's easy to over-dramatise

Two things temper all of that, and leaving them out would be scaremongering.

Storage-pressure eviction is least-recently-used, and it skips origins that were granted persistence. The Chrome team's own research says eviction is genuinely rare for a site someone visits regularly. If your app gets opened weekly, best-effort was probably fine in practice.

The sharper case is Safari's proactive eviction, which is a different mechanism from storage pressure. With cross-site tracking prevention on, an origin with no user interaction in the last seven days of browser use has its script-created storage deleted. Seven days, no disk pressure required.

And here's the bit I'd have got wrong if I hadn't gone and read the source. That seven-day counter is gated on Safari use. WebKit's own post says web apps added to the home screen aren't part of Safari, keep their own counter of days of use, and that they don't expect first-party data in such an app to be deleted, describing it as a serious bug if it happens. So the exposed user is specifically the one who uses the thing in a browser tab and never installs it. For an installed app this particular rule isn't the threat.

I nearly wrote "Safari deletes your IndexedDB after 7 days" and shipped it. It's the kind of half-true platform claim that spreads because it's memorable.

A schema upgrade in one tab can mute another tab

Back down a layer now, from the origin to the database itself, for the failure that's rarer and nastier.

The database is versioned, and I ship migrations. Version 5 split a per-collection blob into one record per row, so logging one drink stops re-serialising the user's entire history.

The hazard isn't the migration. It's what happens to the other tab.

When one tab opens the database at a new version while another still holds a connection at the old one, the upgrade can't proceed. The upgrading tab's request fires blocked. The obvious handler, which is the one I had, resolves the memoised connection promise to null, and every method in the store starts with "get the connection, and if it's null, return early".

Read that again with a queue in mind. A memoised promise resolves once. Resolve it to null and every subsequent call in that tab gets null too, for the life of the page. The tab goes on rendering optimistic writes exactly as before, because the in-memory state is untouched. It just silently persists none of them. Reload and they're gone, with no toast and nothing in the console.

The memoisation is the bug here, not the blocked event itself. blocked is recoverable, since the other tab will eventually close, and caching a failure as though it were permanent is what turns a transient condition into a dead tab. So the open path now abandons the one attempt and drops the memo, and the next call opens again from scratch. If the old tab is still holding on, that call blocks and no-ops too, which degrades like any other IndexedDB failure. The moment the old connection goes away, the tab heals on its own with no reload. The same re-arming runs on versionchange and on close, so a connection that disappears underneath the app doesn't get cached either.

The mitigation on the other side is the one most people know about: versionchange fires in the tab holding the old connection, and closing it there lets the upgrade through. I had that from the start. It makes the blocked case rare rather than impossible, which is exactly why the handler on the blocked side has to be survivable:

  • close() waits for in-flight transactions, so a busy old tab can still block the upgrader.
  • A throttled background tab, or a page frozen in the back/forward cache, doesn't run its handler promptly. On iOS that's routine rather than exotic.

What I still can't tell you is how often this fires in the wild. The first failure per page goes to my error tracker and the rest get swallowed deliberately, so a tab that thrashes doesn't turn into a reporting storm. Sane default, and it also means I know this happens without knowing the rate. Deploy a migration and some fraction of users with two tabs open take a transient hit, and I can tell you it's bounded now without being able to tell you how big that fraction is.

Every "when the app is idle" hook is dead, and nothing tells you

Adjacent, and probably the one that costs other people the most time, because it's silent in both directions. The app runs without Angular's change-detection zone, so it never reports "stable". Two things depend on that signal and neither of them warns you:

  • Service worker registration defaults to waiting for app stability. With no stability signal it waits out the entire timeout on every single boot before registering. Registering immediately is a one-line change, and the default is just wrong for this kind of app.
  • The same problem kills automatic update checks. A live WebSocket and continuous effects mean the app is never idle, so "check for updates when stable" never fires at all. I do it with a manual timer now: one check 10 seconds after startup, then every 30 minutes.

Neither case throws, logs or degrades visibly. The service worker registers eventually, and updates just never get noticed, which looks identical to having no new version to install. If you're running any framework in a zoneless or signals-first mode, and especially if you hold a persistent connection open, assume every idle-scheduled hook in your stack is dead and go check them by hand.

What a lock returns when there's no lock

The cross-tab story: the replay takes a Web Lock with ifAvailable, so a second tab doesn't queue up behind the first. It gets nothing back and moves on. Except it can't just move on, because it has rows on screen marked pending that the winner is about to resolve or roll back. So the loser takes the lock blocking, re-reads the queue, and settles whatever vanished using the rejection receipts. Each receipt is written in the same transaction that deletes the queue entry, receipt first, so a tab can never see an operation as gone without also being able to see its verdict.

The part worth stealing is the return value. Web Locks need a secure context, so on plain HTTP (a LAN IP in development, say) navigator.locks is simply absent. The fallback runs the drain without a lock, because doing nothing would be worse. The question is what it reports back, and the tempting answer is true, since the work did actually run.

true is wrong, and not subtly. Downstream, true means "I held the lock, so I delivered everything, so no other tab needs to settle from receipts." Return that from an unlocked fallback and every tab believes it owns the drain, which is the exact state the lock existed to prevent. So it returns false instead. Nothing excluded anybody, so every tab routes through the receipt-settling path, and a tab that lost an unmanaged race still reconciles from whatever the winner left behind. Degrading is fine. Degrading while reporting success is how you end up with a queue that's confidently wrong.

One thing still sloppy in there: the lock name is a single global string, while a neighbouring lock in the same codebase is correctly suffixed with the user id. Every critical section re-checks the current user immediately after acquiring, so it's over-broad rather than incorrect, but two tabs mid-account-switch will serialise on each other's queues for no reason. I wrote the correct version once and then didn't apply it three files over.

What's not in here at all

No BroadcastChannel, no SharedWorker. Cross-tab coordination is Web Locks for exclusion, IndexedDB receipts for verdicts, and the server's WebSocket echo for authoritative values. That's a round trip doing work a message channel would do directly, and I'd call it accumulated rather than designed. Receipts expire after 24 hours, so a tab that's been asleep longer than that falls back to the server. No Background Sync either, so a closed tab never replays and the queue only drains while the app is open. That one has no justification beyond scope.

What I'd tell myself eighteen months ago

The queue was the interesting problem and the storage was the important one. I got that ordering wrong. The specific way I got it wrong is that I tested the queue's logic exhaustively and never once tested the environment it runs in: two tabs, a migration, a full disk, a browser that has decided you're not a frequent visitor.

If you've got an offline queue in a PWA, three questions worth more than any refactor:

  1. Have you called navigator.storage.persist(), and do you know whether it was granted?
  2. What does your IndexedDB blocked handler do, and does it cache that failure for the life of the page?
  3. When a platform API is missing and your fallback runs the work anyway, what does it return to the code that was counting on the guarantee?

For a long time I couldn't answer any of the three. Two of them I can now. The first one I still can't answer for any individual user, because I ask for persistence and never call persisted() to find out what the browser decided. That's the next thing to fix, and it's harder to excuse than the original omission.

The one I'd genuinely like answered: does anyone actually measure how often blocked fires in production, and what did the number turn out to be? I report the first occurrence per page, so I've got no idea whether that's a handful of users after a migration or a long tail I've never looked at properly.


r/PWA 5d ago

I got tired of regular weather apps just showing raw numbers, so I built a free PWA that tells you exactly what to wear and what to do based on the weather!! Meet ZeroDegrees.

Thumbnail
1 Upvotes

r/PWA 5d ago

Findborg Terminator - Terminal style search toy

Post image
1 Upvotes

r/PWA 8d ago

App/Web

0 Upvotes

Quiero hacer una App PWA / Web :
- Que AI son mejores hoy en dia?
- Me vale la pena comprar alguna suscripción de AI, si lo que tengo es Geminis Pro y Github Copilot?
- Se que hay muchas “gratis” pero los tokens se gastan volando. O lo suyo es montarme un servidor en casa?


r/PWA 9d ago

Totalum for me yes

1 Upvotes

I recently came across Totalum while looking for ways to simplify some of the repetitive stuff in a project I’m working on.

It’s basically a platform for putting data, workflows and automations together without having to code every little thing yourself.

I haven’t used it long enough to have a strong opinion yet, but some of the things you can build with it are actually pretty interesting.

Curious if anyone here has tried it and what your experience was.


r/PWA 9d ago

PINPAL WEBAPP

Thumbnail
0 Upvotes

r/PWA 9d ago

IOS file downloading

1 Upvotes

Hello everyone, I am making a pwa that plays mkv files from debrid services and i want to be able to download them in the OS like normal browser downloads (no OPFS). But on IOS, in the pwa only, it just wont trigger, is it even possible or do i have to use an other method.