The problem
On The Gate helps you find football near you, and a football app feels like it should live on your phone. So the first plan was Flutter: one Dart codebase, compiled for iPhone and Android. For one person working around a day job, that looked like the tidy answer.
Then I added up what stood between me and the first real user. Xcode, which means a Mac. Android Studio. Flutter itself. Then two store reviews, which my notes at the time put at one to seven days each go. All of that before finding out the only thing I actually needed to know: would anyone use this?
On 4 June I dropped it for a Progressive Web App, built with React and Vite. The API was already running in FastAPI and didn’t need to change. The first version of the frontend was scaffolded that same afternoon.
What the decision was really about
“One codebase” wasn’t the argument, because Flutter already offered that. The argument was the cost of getting a change
in front of someone. With a PWA, a release is a git push and a tester is someone you’ve sent a link to.
There’s no store account and no review queue, and nobody has to update anything.
It paid off in smaller ways too. Leaflet, the map library, is built for the web and needs no wrapper. And when the redesign added a split list-and-map view for wide screens, desktop was simply the same site with more room.
I’d love to say this made me fast. The target was live by mid-July, and it went live on 19 October. The web didn’t make the project quick. It just meant that the delay was never a store’s fault.
What makes it an app
Less than you’d think. There are two main pieces:
- A manifest. A small JSON file with the name, icons and
"display": "standalone", which is what makes it open full screen from the home screen with no browser bar. - A service worker. Vite’s build files have a content hash in their names, so they can be served from the cache first without ever going stale. Everything else goes to the network first and only falls back to the cache if that fails.
That split is the important part. Stale fixtures are worse than no fixtures, because you could end up on a train to a game that’s been moved. Signal at a ground can also be poor, so the app is built to still open with no connection. The shell comes from the cache, but the map tiles and live data don’t. It’s honest, if not magic.
The bill
Skipping the store isn’t free. You swap one set of problems for another, and most of mine have been on iPhones.
Installing is on you. Chrome, Edge and Samsung Internet fire a beforeinstallprompt event, so on
Android and desktop I can show my own Install button. iOS never fires it. The only route there is Share, then Add to
Home Screen, so the app has to explain those two taps itself. It also waits to ask: never on a first visit, only from
the second visit or straight after you follow a club or save a game, and “Not now” buys you 14 days of peace. Nobody
is browsing a store and stumbling across it either, so finding the app is now entirely my problem.
The hard bugs only exist on the device. My first test on a real phone came on launch day and threw up a gap at the tab bar, but only in the installed iPhone app. My first fix wasn’t enough. It turned out to be a WebKit bug in iOS 26, where an installed web app’s viewport can come up short by the height of the status bar. The Playwright suite can’t see any of this, because it only happens in a real home-screen install. So the second fix is still waiting on me to check it on the phone, which is the sort of test no pipeline will run for you.
The general lesson
When you don’t yet know if anyone wants the thing, choose the route that makes each change cheapest to ship, even if the result is a bit less polished. If On The Gate ever needs something only a native app can do, that’ll be a good problem to have, and I’ll be solving it knowing that people actually turn up. Non-league clubs have long understood this: get the gate through the turnstile first, then worry about the new stand.