Skip to content

v3.8.1 — Two production 404s (site root, consent banner) + link crawler

Choose a tag to compare

@thunpisit thunpisit released this 31 Jul 12:46
· 42 commits to main since this release

Two live 404s that a browser never shows you, found by driving the deployed site instead of reading source.

Includes everything in v3.8.0 — cookie prefix fix (#120) and mobile UX. Deploying still logs every admin out once; announce before rolling out.


🔴 The site root 404'd for Accept-Language: *

GET / returned 404 to any client sending the wildcard — including Node's fetch, which sends it by default. curl worked, so the site looked healthy from a terminal.

curl -sI  https://…/            → Location: /en   ✅
node -e 'fetch("https://…/")'   → 404 at /*       ❌

The guard was inert:

if (lang && SUPPORTED_LOCALES.includes(toLocale(lang)))
  throw redirect(308, `/${lang}`);   // ← raw tag, not the validated one

toLocale() coerces anything unknown to DEFAULT_LOCALE, so includes(toLocale(x)) is true for every input — then the redirect used the raw tag:

Accept-Language Guard Redirects to
en-US true /en
* true /* ❌ 404
zz true /zz ❌ 404

* is legal per RFC 9110 §12.5.4, so this was real traffic — uptime monitors, crawlers, and any client not sending a specific language got a 404 homepage. If you run synthetic monitoring against /, check whether it has been silently failing.

🔴 The consent banner linked to a 404

The privacy-policy href was hardcoded, so any install without a page at that slug shipped a broken link on its GDPR consent banner — the one link there that legally ought to resolve.

The layout now looks the page up and passes an href only when a published page exists; the banner omits the link otherwise. Looked up rather than configured, so it self-corrects when you publish or unpublish — no redeploy. A draft policy deliberately doesn't count.

Check your own install: if you have no published page at /privacy-policy, that link has been 404ing.

🔧 Link crawler

node scripts/smoke-links.mjs https://your-site.example

Crawls a deployed install and exits non-zero on any broken internal link. It found the first defect and independently reproduced the second in both locales across 17 pages.

Unit tests don't follow hrefs — only a crawler finds "this link points at nothing". Worth wiring into your deploy pipeline.

📐 163-character lines

At 1920px the banner copy ran the full viewport width — measured 163ch against the 45–75ch typographic guideline. Past ~90ch the eye loses its place returning to the next line: the worst property to give text asking for a privacy decision.


🚀 Upgrading

git fetch upstream && git merge upstream/main

No migration, no config change. Announce the forced logout (from v3.8.0's cookie change) and deploy.

Setup instructions — what belongs at /admin/settings/secrets vs the Cloudflare dashboard — are unchanged from v3.7.1.

Tests

121 (was 109). Locale and consent-banner guards are mutation-verified; the locale test keeps the original defective predicate alongside the fix so the failure mode stays documented.

Full changelog: v3.8.0...v3.8.1