Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions tools/lib/projections.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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. ---
Expand Down
30 changes: 30 additions & 0 deletions tools/lib/render/landing.mjs
Original file line number Diff line number Diff line change
@@ -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 {
Expand Down Expand Up @@ -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:
`<div class="hero"><h1>Page not found</h1>` +
`<p class="lede">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.</p>` +
heroSearch() +
`<p class="nf-links">` +
`<a href="/">Home</a> &middot; <a href="/documentation/">Documentation</a> &middot; ` +
`<a href="/training/">Training</a> &middot; <a href="/browse/">Browse</a></p>` +
`</div>`,
nav,
layout: 'wide',
hideHeaderSearch: true,
}),
'utf8',
);

// --- section landings -------------------------------------------------------
writePage(
outDir,
Expand Down