4 min read

The 404 that only strangers could see

Someone told me a Split invite link I'd sent them just didn't work. A blank 404 page. I opened the exact same link on my own phone and it loaded perfectly — invite preview, group name, all of it.

That contradiction is the whole story. The link wasn't broken. My relationship to the link was different from theirs, and that difference turned out to be the entire bug.

The setup

MyFinCup's Split feature lets you invite someone to a shared bill through a link — something like myfincup.com/split-invite/abc123. That page is deliberately open to anyone, logged in or not, since an invite has to work for someone who's never used the app before.

I tested it. It worked. I sent it to a few people while building it, and it worked for them too. It shipped. Weeks later, a real invite went to someone who had genuinely never opened MyFinCup before — the exact audience an invite link exists for — and they got a 404.

Why it worked for me and broke for them

The cause was almost embarrassingly small: there was no routing configuration telling my hosting provider what to do with a URL that isn't a real file on disk.

Most hosts serve a page only if it matches an actual file. The homepage works because there's literally a file there. But a page like the invite link only exists inside the app itself, built on the fly once the app has loaded in your browser. Without a rule saying "if the path isn't a real file, just load the app and let it figure out the rest," the very first request for that URL gets a flat 404 — before a single line of my code ever runs.

Here's the part that hid it from me for weeks: once a browser has visited the app even one time, every future visit — clicking a link, typing a new URL, even opening something for the first time — gets intercepted by the app that's already loaded and cached on that device, instead of going back to the server at all. My phone, my laptop, my test browser had all opened MyFinCup dozens of times before I ever tested an invite link. There was no way for me to hit the bug, because by the time I tested the feature, none of my devices remembered what a truly first visit even looked like.

The person who reported it had never opened the site before. Their very first request to the domain was a request for a page that doesn't exist as a file. No cached app to fall back on — just a 404.

That's what makes this kind of bug genuinely dangerous rather than just an oversight: it doesn't fail for a random slice of people. It fails, every single time, for exactly the group that matters most for an invite feature — people who've never used the app — while looking completely fine to everyone who already has it, which by definition includes whoever built and tested it.

The fix, and its own catch

The fix itself is small: one routing rule telling the host to fall back to the main app for any path that isn't a real file. It doesn't touch anything that's supposed to load directly — images, icons, the manifest — those keep working exactly as before.

The harder part is that this bug is basically impossible to catch by testing locally, because your own development setup already does the right thing by default — the very behavior missing in the real deployment. You can run every check available and watch it pass, because the tool you're testing with was never broken in the first place. The only real way to confirm the fix landed is to open a link you've genuinely never visited, in a browser with zero memory of your app, and see what actually happens.

The takeaway

The lesson isn't "remember the routing file" — that's a checklist item, and checklist items get forgotten again on the next project. The real lesson is sharper: any bug that only shows up for people whose browser is in a different state than yours is a bug your own testing can't catch, no matter how careful you are — because the one thing that would reveal it, a genuinely first-ever visit, is a state your own process destroys the moment you start testing.

I check every link people can click cold that way now — a private window that's never met the app before. It's the only test that actually asks the same question a stranger's browser is going to ask.

Next: the story behind Split itself — what I borrowed from a feature I'd already built once for a different app, what I hardened for a finance app specifically, and the one bug that snuck through even after I thought I'd checked for it.