A 308 that sent every link home
Reloading any page in the app, or opening a shared link, landed on Today. The SPA rewrite rules pointed at /index.html, and Cloudflare Pages redirects that to /. It had been true for every deployment.
While verifying the new day view in production, /day/2026-10-02 came back
404, and /search came back with a 308 to /. Checking older routes
showed it wasn’t new: /habits did the same, on every deployment going
back as far as the list went.
What was happening
The app is a single-page app on Cloudflare Pages. A generated _redirects
file rewrites each known route prefix to the app shell:
/habits /index.html 200
/habits/* /index.html 200
A 200 rule is meant to serve the target file in place, with no redirect.
But Pages also normalises URLs: a request for /index.html is redirected
to / with a 308. That normalisation applied to the rewrite target too.
So every deep link became a redirect to the home page.
In daily use it hid well. Moving around inside the app never reloads the page, so it worked. It only broke on a reload, a link opened from outside, or a home-screen shortcut, and each of those looks like a minor glitch rather than a bug.
The fix
Rewrite to / instead, which serves the same file:
/habits / 200
/habits/* / 200
It went to a preview branch first. All six routes tested returned 200
with the app shell, including nested routes and query strings. An unknown
path still returned a real 404, and robots.txt was still plain text.
Then it was merged.
The lesson
Checking a route with curl that follows redirects, or in a browser,
would have shown the page loading and nothing wrong. Checking the status
code without following redirects is what showed the 308. Deep-link
checks now look at the status, not just the final page.
AlzyWelzy
Building PaceStreak. GitHub
Related
Security & PrivacyFixing the dead ends in sign-up and verification
Three real bugs found by using the OTP flow the way a person actually would: a stuck login, a false 'already registered' error, and a confirmation screen that didn't lead anywhere. Plus a status page that finally watches the whole stack.
3 min
InfrastructureHow much one small server carries
A load test of signed-in users doing what the app does all day, the first capacity numbers, and why production numbers from India say more about distance than about the server.
2 min
DesignKeyboard first, and thumb first
A command palette, G-then-a-letter shortcuts, swipe and long-press on habits, pull to refresh, and why each one is a shortcut on top of a button rather than the only way in.
2 min