Every URL on My Site Returns 200, Including the Ones That Don't Exist

· 5 min read · by Michael Kaminski

Behind the Build, Vol. 7: I had to prove four recovered articles were actually live, and discovered my own verification was a test that could never fail. The fix came from a filename.

Today was cleanup day. Several articles had been written over the past week and never actually reached the site — each one sitting in its own branch that quietly never merged. Recovering the text was the easy part: it was all still there, nothing had been deleted, and putting it back was an exercise in careful copying.

Proving it had actually gone live turned out to be the genuinely interesting problem.

The test that always passes

My first instinct was the obvious one. Ask the site. Request the URL of a previously-missing article, look at the status code, and let HTTP answer the question: 200 means it's there, 404 means it isn't.

Every path I asked about returned 200. Including several I invented on the spot.

This isn't a bug. It's the single-page-app bargain, and I configured it myself years ago. There's a rewrite rule sending every incoming request to index.html, because the server has no idea which article slugs exist — that knowledge lives inside a JavaScript bundle that hasn't run yet. So the server says 200 to absolutely everything and lets the browser's router sort out what's real.

Which means a "does this page exist" check built on status codes is a test that passes regardless of the answer. That's worse than having no test at all. No test is a known gap; a test that can't fail is a gap wearing a badge that says everything's fine. I very nearly wrote "verified, all four articles are live" on the strength of four 200s that would have looked exactly the same if the deploy had never happened.

Asking a question that can come back no

The thing worth checking was never the route. It was the payload — and the payload had a property I'd been ignoring for years because it mostly just looks like noise.

The build tool names compiled files after a hash of their contents. Not a version, not a timestamp: a fingerprint. Change one character of one article and the filename changes with it. That's normally a cache-busting detail nobody thinks about, but it turns the filename into something much more useful — a claim about content that the file itself has to honor.

So the question became answerable. Build locally, note which file the article data landed in, then fetch that exact filename from the live domain and compare the two byte for byte. If production is serving a file with the same content hash as the one on my laptop, it isn't serving something similar or something recent. It's serving the same bytes.

They matched — identical, to the byte — and all nine articles were in it. That's a verification that had a real opportunity to come back negative, which is the entire property I wanted and the entire property the status-code check lacked.

The part I got wrong first

Worth admitting: my first pass at the payload check also misfired. I grepped the main bundle for the article slugs and found the four new ones present and the five older ones apparently missing, which briefly looked like I'd shipped a catastrophe — the exact overwrite I'd spent the day repairing.

I hadn't. The article data had been split into a separate lazy-loaded chunk, and the main bundle only contained a few slug strings for unrelated routing reasons. I was grepping a file that was never going to have the answer, and reading its silence as evidence. Same category of mistake as the 200s, honestly: I asked a source that couldn't possibly know, then treated its response as informative.

The lesson

Both mistakes came from the same reflex — reaching for the check that's easy to run rather than the one that's hard to fool. A status code is one command. A content-hash comparison takes a few more steps and an understanding of how your own bundler works, which is precisely why it's worth something.

The useful question to ask of any verification, before trusting it: what would this look like if the thing I'm checking were broken? If the honest answer is "about the same," you haven't verified anything. You've just performed the shape of verifying, which is a surprisingly comfortable place to stop.

Next time in Behind the Build: hopefully something I got right on the first attempt. The historical record here is not encouraging.

All writing · Projects · Michael Kaminski


Book a Call · Download Resume · MKaminski1337@gmail.com · (404) 838-8613 · LinkedIn · GitHub · DEV