From 1b4e1d7050409b63dd289d637e1aee39d404d83d Mon Sep 17 00:00:00 2001 From: 0thernet Date: Thu, 20 Aug 2026 18:12:17 +0000 Subject: [PATCH] fix: keep 404 metadata off the homepage Root layout kept the homepage title, canonical, and indexable robots, so unmatched routes reused that identity. Move homepage identity onto the homepage and give not-found its own noindex page. --- app/layout.tsx | 26 +++++++++++++++++--------- app/metadata.test.ts | 29 ++++++++++++++++++++--------- app/not-found.test.ts | 16 ++++++++++++++++ app/not-found.tsx | 15 +++++++++++++-- app/page.test.tsx | 4 ++++ app/page.tsx | 2 ++ 6 files changed, 72 insertions(+), 20 deletions(-) create mode 100644 app/not-found.test.ts diff --git a/app/layout.tsx b/app/layout.tsx index 5ff1925..c2ba73f 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -4,29 +4,37 @@ import { SkipLink, ThemeColorSync, } from "@/support/theme"; -import { INDEXABLE_ROBOTS } from "@hraness/web-discovery"; import { JsonLdScript } from "@hraness/web-discovery/json-ld"; import type { Metadata, Viewport } from "next"; import type { ReactNode } from "react"; import "./globals.css"; import { PostHogAnalytics } from "./posthog-analytics"; import { websiteJsonLd } from "./seo"; -import { SITE_ORIGIN, site, socialMetadata } from "./site"; +import { SITE_ORIGIN, site } from "./site"; export const metadata: Metadata = { metadataBase: new URL(SITE_ORIGIN), title: { - default: site.title, + default: site.applicationName, template: site.titleTemplate, }, - description: site.description, applicationName: site.applicationName, - alternates: { - canonical: "/", - }, formatDetection: { address: false, email: false, telephone: false }, - robots: INDEXABLE_ROBOTS, - ...socialMetadata(site.title, site.description, "/"), + openGraph: { + type: "website", + locale: "en_US", + siteName: site.name, + images: [{ + alt: site.socialImageAlt, + height: 630, + url: "/opengraph-image", + width: 1200, + }], + }, + twitter: { + card: "summary_large_image", + images: [{ alt: site.socialImageAlt, url: "/opengraph-image" }], + }, }; export const viewport: Viewport = { diff --git a/app/metadata.test.ts b/app/metadata.test.ts index ff1fd06..3656664 100644 --- a/app/metadata.test.ts +++ b/app/metadata.test.ts @@ -53,10 +53,9 @@ describe("stripehistory.com public identity", () => { }, sitemap: `${SITE_ORIGIN}/sitemap.xml`, }); - expect(metadata.robots).toMatchObject({ index: true, follow: true }); }); - test("keeps canonical metadata on stripehistory.com", () => { + test("keeps only site-wide defaults that other routes can inherit", () => { expect(robots()).toMatchObject({ host: SITE_ORIGIN }); expect(manifest()).toMatchObject({ description: site.description, @@ -68,14 +67,26 @@ describe("stripehistory.com public identity", () => { expect(new URL(metadata.metadataBase ?? "https://invalid.example").origin).toBe( SITE_ORIGIN, ); - expect(metadata.alternates).toEqual({ canonical: "/" }); - expect(metadata.title).toEqual({ - default: site.title, - template: site.titleTemplate, + expect(metadata).toMatchObject({ + applicationName: site.applicationName, + title: { + default: site.applicationName, + template: site.titleTemplate, + }, + openGraph: { + locale: "en_US", + siteName: site.name, + type: "website", + }, }); - expect(metadata.description).toBe(site.description); - expect(metadata.openGraph?.title).toBe(site.title); - expect(metadata.twitter?.title).toBe(site.title); + expect(metadata).not.toHaveProperty("description"); + expect(metadata).not.toHaveProperty("robots"); + expect(metadata.alternates).toBeUndefined(); + expect(metadata.openGraph).not.toHaveProperty("url"); + expect(metadata.openGraph).not.toHaveProperty("title"); + expect(metadata.openGraph).not.toHaveProperty("description"); + expect(metadata.twitter).not.toHaveProperty("title"); + expect(metadata.twitter).not.toHaveProperty("description"); expect(socialImageAlt).toBe(site.socialImageAlt); }); diff --git a/app/not-found.test.ts b/app/not-found.test.ts new file mode 100644 index 0000000..fd6659a --- /dev/null +++ b/app/not-found.test.ts @@ -0,0 +1,16 @@ +import { describe, expect, test } from "bun:test"; +import { NOINDEX_ROBOTS } from "@hraness/web-discovery"; + +import { metadata } from "./not-found"; + +describe("stripehistory.com not-found metadata", () => { + test("uses a distinct noindex page instead of the homepage identity", () => { + expect(metadata).toEqual({ + title: "Page not found", + description: "The requested Stripe history page does not exist.", + robots: NOINDEX_ROBOTS, + }); + expect(metadata.alternates).toBeUndefined(); + expect(metadata.openGraph).toBeUndefined(); + }); +}); diff --git a/app/not-found.tsx b/app/not-found.tsx index 63b9e27..a551848 100644 --- a/app/not-found.tsx +++ b/app/not-found.tsx @@ -1,15 +1,26 @@ +import { NOINDEX_ROBOTS } from "@hraness/web-discovery"; +import type { Metadata } from "next"; import Link from "next/link"; import { SiteFooter } from "./site-footer"; import { SiteHeader } from "./site-header"; +const notFoundTitle = "Page not found"; +const notFoundDescription = "The requested Stripe history page does not exist."; + +export const metadata: Metadata = { + title: notFoundTitle, + description: notFoundDescription, + robots: NOINDEX_ROBOTS, +}; + export default function NotFound() { return (
-

Page not found

-

The requested Stripe history page does not exist.

+

{notFoundTitle}

+

{notFoundDescription}

Browse Stripe company history

diff --git a/app/page.test.tsx b/app/page.test.tsx index 0d85dff..5853b7b 100644 --- a/app/page.test.tsx +++ b/app/page.test.tsx @@ -1,8 +1,10 @@ import { describe, expect, test } from "bun:test"; +import { INDEXABLE_ROBOTS } from "@hraness/web-discovery"; import { loadHistory } from "@/lib/content"; import { renderToStaticMarkup } from "react-dom/server"; import Home, { generateMetadata } from "./page"; +import { site } from "./site"; describe("canonical stripehistory.com history", () => { test("publishes root-canonical history metadata", async () => { @@ -12,6 +14,8 @@ describe("canonical stripehistory.com history", () => { expect(metadata).toMatchObject({ alternates: { canonical: "/" }, + description: site.description, + robots: INDEXABLE_ROBOTS, title: expectedTitle, }); expect(metadata.openGraph).toMatchObject({ diff --git a/app/page.tsx b/app/page.tsx index b263cd3..b6bbee4 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -1,4 +1,5 @@ import { loadHistory } from "@/lib/content"; +import { INDEXABLE_ROBOTS } from "@hraness/web-discovery"; import { JsonLdScript } from "@hraness/web-discovery/json-ld"; import type { Metadata } from "next"; @@ -19,6 +20,7 @@ export async function generateMetadata(): Promise { title, description: site.description, alternates: { canonical: "/" }, + robots: INDEXABLE_ROBOTS, ...socialMetadata(`${title} | ${site.domain}`, site.description, "/"), }; }