Resilience: 404/500/503 pages, dynamic web app manifest, X-Redirect-By - #38
Merged
Merged
Conversation
Implements the Resilience section of specification.website. - Custom 404/500/503 pages via a zero-JS ErrorLayout (no importmap/Lottie/ bundle), noindex, same header/nav/footer chrome. 404 wired into the fallback; 500 via a new handleGuarded() pipeline (maintenance gate -> try/catch -> styled 500 with server-side logging, no stack-trace leak) that both secureRoutes and the fetch fallback route through, plus a Bun.serve error() backstop. - 503 maintenance mode: MAINTENANCE_MODE=true serves a 503 + Retry-After for all paths except /health (documented in .env.example). - Web app manifest served dynamically from SITE_NAME (never stuck as "Billet"): buildWebManifest() + /site.webmanifest route with application/manifest+json, start_url, and a maskable icon. Deleted the static public/site.webmanifest. - X-Redirect-By stamped centrally in withSecurityHeaders for any redirect (redirect() helper, 308 trailing-slash, auth/admin 303s). - START_PROMPT note: manifest + X-Redirect-By follow SITE_NAME automatically. bun run check clean; 321 tests pass (added error/manifest/maintenance/ X-Redirect-By coverage). Verified live in-browser (404/500/503/manifest). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Implements the Resilience section of specification.website, the next section in the ongoing spec audit (Foundations, SEO, Accessibility, Security, Performance, Privacy already done).
What's in here
Custom error pages — 404 / 500 (Required)
ErrorLayout— same header/nav/footer chrome as the site, but ships zero client JS (no importmap, Lottie, or main bundle) so error pages render instantly even when the app is degraded, and alwaysnoindex. Extracted a sharedSiteFooterso the repo-specific links live in one place.templates/error.tsx+utils/errors.tsxrenderrender404()/render500()/render503().fallback.tsnow returns the styled, navigable page instead of plain-text"Not found"(correct status, homepage link, no leaks).handleGuarded()wraps both the routed handlers and the fallback — it catches any uncaught error, logs it server-side, and returns the styled 500 with no stack-trace / path leak. ABun.serveerror()backstop also suppresses Bun's dev error page (which does leak).503 maintenance (Recommended)
MAINTENANCE_MODE=trueserves a 503 with aRetry-Afterheader (default 3600s, override viaMAINTENANCE_RETRY_AFTER) for every request except/health(so the platform doesn't cycle the instance). Documented in.env.example.Web app manifest (Recommended)
buildWebManifest()driven bySITE_NAME(+start_url,scope,display: standalone, theme/bg colours, 192/512 icons, and a maskable icon), served at/site.webmanifestwithapplication/manifest+json.X-Redirect-Byboth followSITE_NAME— renaming that one constant renames both.X-Redirect-By (Recommended)
withSecurityHeaders: any response with aLocationheader getsX-Redirect-By: <SITE_NAME>. One line covers every redirect — theredirect()helper, the 308 trailing-slash, and the inline 303s in the auth/admin middleware.Graceful degradation (Recommended)
<a>/<form action>, the single projects-search island degrades to the server-rendered list). Reinforced by making the new error/maintenance pages zero-JS.Deferred (Optional / infra)
Service worker / offline (skipped by request); Deprecation/Sunset headers (no versioned endpoints yet); external synthetic monitoring, separate-host status page, and RUM/web-vitals (ops/infra — the
/healthliveness endpoint already exists).Testing
bun run checkclean; 321 tests pass (addederrors.test.tsx,webmanifest.test.ts, plus X-Redirect-By andhandleGuardedcases; updatedfallback.test.ts).application/manifest+json, name/short_name/start_url/maskable).How to test the error pages locally
/nope.MAINTENANCE_MODE=truein.env, restart — every page 503s except/health; unset to lift.throw new Error("preview 500")in a controller (e.g.home.index()), hit the page, remove it. The real error logs to the terminal, never to the user.🤖 Generated with Claude Code