From ab7f1a4187b516b93112609489bf9beb0535eb21 Mon Sep 17 00:00:00 2001 From: bahadirarda Date: Thu, 30 Jul 2026 11:35:07 +0300 Subject: [PATCH] feat(build): emit a real 404 page + _redirects to end the soft-404 --- tools/lib/projections.mjs | 9 +++++++++ tools/lib/render/landing.mjs | 30 ++++++++++++++++++++++++++++++ 2 files changed, 39 insertions(+) diff --git a/tools/lib/projections.mjs b/tools/lib/projections.mjs index 1a8cc5c..a66a943 100644 --- a/tools/lib/projections.mjs +++ b/tools/lib/projections.mjs @@ -238,6 +238,15 @@ export function emitProjections(vm, { outDir, ROOT, HERE, assets }) { ); writeFileSync(join(outDir, 'robots.txt'), `User-agent: *\nAllow: /\n\nSitemap: ${SITE_URL}/sitemap.xml\n`, 'utf8'); + // --- _redirects: turn the Cloudflare Pages soft-404 into a real 404. Pages + // evaluates this only for a path that matches no static asset and no Function, + // so every real page (served as /path/index.html) and the /mcp Function are + // untouched; only a genuine miss - a mistyped page, a stale or nonexistent + // /_api/{uid}.json an agent requested - falls through to the splat and is + // served the 404.html page with a 404 status, instead of a 200 home page that + // hides the miss. Last-match-wins, so this single catch-all is the whole file. + writeFileSync(join(outDir, '_redirects'), '/* /404.html 404\n', 'utf8'); + // --- _headers: security headers applied by Cloudflare Pages to every path. // Inline theme resolver + mermaid's injected SVG styles need 'unsafe-inline'; // everything else is same-origin only, framing and object embeds are denied. --- diff --git a/tools/lib/render/landing.mjs b/tools/lib/render/landing.mjs index 9ada3c6..bee7256 100644 --- a/tools/lib/render/landing.mjs +++ b/tools/lib/render/landing.mjs @@ -1,3 +1,6 @@ +import { writeFileSync } from 'node:fs'; +import { join } from 'node:path'; + import { escapeHtml } from '../markdown.mjs'; import { icon } from '../icons.mjs'; import { @@ -226,6 +229,33 @@ export function renderLanding(vm, outDir) { }), ); + // --- 404: served by Cloudflare Pages for any path with no static asset, so a + // stale or mistyped URL - including a missing /_api/{uid}.json an agent asked + // for - lands on a real not-found page with a 404 status (paired with the + // /* -> /404.html 404 rule in _redirects), not a 200 masquerading as the home + // page. Written to the site root as 404.html, which is the name Pages looks + // for, rather than through writePage (which would nest it at /404/index.html). + writeFileSync( + join(outDir, '404.html'), + shell({ + title: 'Page not found', + description: 'That page is not here. Search the corpus or start from a section.', + body: + `

Page not found

` + + `

That page is not here - it may have moved, or the link may be wrong. ` + + `Search the corpus, or head back to a main section.

` + + heroSearch() + + `` + + `
`, + nav, + layout: 'wide', + hideHeaderSearch: true, + }), + 'utf8', + ); + // --- section landings ------------------------------------------------------- writePage( outDir,