fix(web): 404 fallback returns a real 404, not a soft-404 200 (BUNYIP-186) #205

Merged
nrupard merged 1 commit from fix/BUNYIP-186-not-found-404-status into main 2026-06-23 19:38:58 +02:00
Owner

What

Makes bunyip-web's catch-all fallback return a real HTTP 404 instead of a soft-404 (200), while still rendering the branded "Page not found" page (BUNYIP-186).

Why

not_found (handlers/public.rs) returned the 404 page via html(...) = HTTP 200, so every unmatched path answered 200 OK. Soft-404s mislabel missing resources to crawlers / monitoring / caches and hide routing gaps. It also directly complicated BUNYIP-185: a fallback that returns 200 for any path made a status-only /healthz liveness probe meaningless, forcing it to validate the response body.

How

  • New web::html_status(markup, StatusCode) helper (mirrors html(); sets the status on the built response).
  • not_found now returns StatusCode::NOT_FOUND with the same page.

Only the fallback changes; real routes are untouched. Nothing depended on the old 200 - the /healthz probe already body-validates.

Verified

clippy -p bunyip-web --all-targets -D warnings + fmt --all --check clean.

🤖 Generated with Claude Code

## What Makes bunyip-web's catch-all fallback return a real **HTTP 404** instead of a soft-404 (200), while still rendering the branded "Page not found" page (BUNYIP-186). ## Why `not_found` (`handlers/public.rs`) returned the 404 page via `html(...)` = HTTP 200, so every unmatched path answered `200 OK`. Soft-404s mislabel missing resources to crawlers / monitoring / caches and hide routing gaps. It also directly complicated BUNYIP-185: a fallback that returns 200 for *any* path made a status-only `/healthz` liveness probe meaningless, forcing it to validate the response body. ## How - New `web::html_status(markup, StatusCode)` helper (mirrors `html()`; sets the status on the built response). - `not_found` now returns `StatusCode::NOT_FOUND` with the same page. Only the fallback changes; real routes are untouched. Nothing depended on the old 200 - the `/healthz` probe already body-validates. ## Verified `clippy -p bunyip-web --all-targets -D warnings` + `fmt --all --check` clean. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
fix(web): 404 fallback returns a real 404, not a soft-404 200
All checks were successful
E2E / Playwright against deployment (pull_request) Successful in 30s
Check / fmt + clippy + build + tests (pull_request) Successful in 4m20s
Create release / Create release from merged PR (pull_request) Has been skipped
ad81fa89d1
bunyip-web's catch-all `not_found` rendered the "Page not found" page but returned it via `html(...)`, i.e. HTTP 200 - so every unmatched path answered 200 + 404 HTML. That mislabels missing resources to crawlers/monitoring/caches, hides routing gaps, and is what forced the BUNYIP-185 /healthz probe to body-validate (a 200-for-any-path fallback made a status-only liveness check meaningless).

Add a `web::html_status(markup, StatusCode)` helper (mirrors `html()`) and have `not_found` return `StatusCode::NOT_FOUND` with the same branded page. Only the fallback changes; real routes are untouched. Nothing depended on the old 200 (the /healthz probe already body-validates).

clippy -p bunyip-web --all-targets -D warnings + fmt --check clean.

#BUNYIP-186

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
nrupard deleted branch fix/BUNYIP-186-not-found-404-status 2026-06-23 19:38:58 +02:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
psa-systems/bunyip!205
No description provided.