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 <noreply@anthropic.com>
This commit is contained in:
2026-10-23 22:18:53 +00:00
co-authored by Claude Opus 5.5
parent 5a4ff08d11
commit d18ca38882
3 changed files with 28 additions and 3 deletions
+3 -2
View File
@@ -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) => {
+23
View File
@@ -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}
<main class="shell py-10">
<section class="card p-6 sm:p-10 text-center bg-gradient-to-b from-white to-indigo-50/50">
<img src="https://vibegif.direct-img.link/detective/clue.webp" alt="Detective searching for clues with a magnifying glass" width="256" height="256" class="mx-auto h-48 w-48" />
<p class="tag mt-6 mb-3"><i data-lucide="search" class="h-3.5 w-3.5"></i>404</p>
<h1 class="text-4xl sm:text-5xl font-bold leading-tight">No pattern here</h1>
<p class="mt-4 text-zinc-700 max-w-xl mx-auto">This page doesn't exist or has moved. Even pattern seekers hit a dead end sometimes.</p>
<a href="/" class="mt-6 inline-flex items-center gap-2 rounded-xl bg-accent px-5 py-3 text-sm font-medium text-white transition hover:bg-indigo-700">
<i data-lucide="arrow-left" class="h-4 w-4"></i> Back to Home
</a>
</section>
</main>
${footer}
`;
export const renderNewsletterPage = () => `
${shellHead({
title: "Newsletter — apophenia.news",
+2 -1
View File
@@ -72,7 +72,7 @@ export const merch = `
</section>
`;
export const shellHead = ({ title, desc, image, url, type = "website" }) => `<!doctype html>
export const shellHead = ({ title, desc, image, url, type = "website", noindex = false }) => `<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
@@ -85,6 +85,7 @@ export const shellHead = ({ title, desc, image, url, type = "website" }) => `<!d
${url ? `<meta property="og:url" content="${url}" />` : ""}
${image ? `<meta property="og:image" content="${image}" />` : ""}
<meta name="theme-color" content="#fcfcfa" />
${noindex ? `<meta name="robots" content="noindex" />` : ""}
${url ? `<link rel="canonical" href="${url}" />` : ""}
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<link rel="alternate" type="application/rss+xml" title="apophenia.news RSS" href="${SITE_URL}/rss.xml" />