From d18ca3888231df4de0ca64bf402f3c6383bf0623 Mon Sep 17 00:00:00 2001 From: "planetrenox@protonmail.com" Date: Fri, 23 Oct 2026 22:18:53 +0000 Subject: [PATCH] Feat: Add a 404 page so unknown URLs stop serving the homepage Cloudflare Pages falls back to the homepage with a 200 when there's no 404.html, which Google treats as soft 404s. The generated 404.html is noindex and shows a detective gif from vibegif. Co-Authored-By: Claude Opus 5.5 --- scripts/generate-pages.mjs | 5 +++-- scripts/lib/renderers.mjs | 23 +++++++++++++++++++++++ scripts/lib/templates.mjs | 3 ++- 3 files changed, 28 insertions(+), 3 deletions(-) diff --git a/scripts/generate-pages.mjs b/scripts/generate-pages.mjs index 4a1b152..a0dd85f 100644 --- a/scripts/generate-pages.mjs +++ b/scripts/generate-pages.mjs @@ -6,7 +6,7 @@ import { marked } from "marked"; import markedKatex from "marked-katex-extension"; import { ARTICLES_GLOB, AUTHOR_PAGES, ROOT, SRC, UNLISTED_PATH } from "./lib/constants.mjs"; import { fixInternalLinks } from "./lib/utils.mjs"; -import { renderArticle, renderHome, renderWritePage, renderNewsletterPage, renderUnsubscribePage, renderAuthorPage } from "./lib/renderers.mjs"; +import { renderArticle, renderHome, renderWritePage, renderNewsletterPage, renderUnsubscribePage, renderAuthorPage, renderNotFound } from "./lib/renderers.mjs"; import { ensureCleanGenerated, writeDiscoveryFiles, writePage } from "./lib/io.mjs"; marked.use(markedKatex({ throwOnError: false, nonStandard: true })); @@ -67,6 +67,7 @@ const run = async () => { await writePage([SRC, "write", "index.html"], renderWritePage()); await writePage([SRC, "newsletter", "index.html"], renderNewsletterPage()); await writePage([SRC, "newsletter", "unsubscribe.html"], renderUnsubscribePage()); + await writePage([SRC, "404.html"], renderNotFound()); for (const author of AUTHOR_PAGES) { await writePage([SRC, "author", author.slug, "index.html"], renderAuthorPage(author)); @@ -79,7 +80,7 @@ const run = async () => { await writeDiscoveryFiles(listedArticles, AUTHOR_PAGES); - console.log(`Generated ${articles.length} article pages (${listedArticles.length} listed) + home + write + newsletter + unsubscribe + author pages + rss/sitemap/robots.`); + console.log(`Generated ${articles.length} article pages (${listedArticles.length} listed) + home + write + newsletter + unsubscribe + author pages + 404 + rss/sitemap/robots.`); }; run().catch((err) => { diff --git a/scripts/lib/renderers.mjs b/scripts/lib/renderers.mjs index 78aeee3..56acb89 100644 --- a/scripts/lib/renderers.mjs +++ b/scripts/lib/renderers.mjs @@ -95,6 +95,29 @@ ${nav} ${footer} `; +// Served by Cloudflare Pages with a 404 status for any path that doesn't exist (instead of falling back to the homepage) +export const renderNotFound = () => ` +${shellHead({ + title: "Page not found — apophenia.news", + desc: "This page doesn't exist.", + type: "website", + noindex: true +})} +${nav} +
+
+ Detective searching for clues with a magnifying glass +

404

+

No pattern here

+

This page doesn't exist or has moved. Even pattern seekers hit a dead end sometimes.

+ + Back to Home + +
+
+${footer} +`; + export const renderNewsletterPage = () => ` ${shellHead({ title: "Newsletter — apophenia.news", diff --git a/scripts/lib/templates.mjs b/scripts/lib/templates.mjs index 81173a5..e343ecd 100644 --- a/scripts/lib/templates.mjs +++ b/scripts/lib/templates.mjs @@ -72,7 +72,7 @@ export const merch = ` `; -export const shellHead = ({ title, desc, image, url, type = "website" }) => ` +export const shellHead = ({ title, desc, image, url, type = "website", noindex = false }) => ` @@ -85,6 +85,7 @@ export const shellHead = ({ title, desc, image, url, type = "website" }) => `` : ""} ${image ? `` : ""} + ${noindex ? `` : ""} ${url ? `` : ""}