From ddf3bd82d3137943b3bf48773569d7bab2b14816 Mon Sep 17 00:00:00 2001 From: Florent Tapponnier Date: Fri, 26 Jun 2026 18:00:57 +0200 Subject: [PATCH] fix(seo): 404 compare pages with hex builder slugs + add curated pairs to sitemap --- src/app/compare/[slug]/page.tsx | 7 +++++++ src/app/sitemap.ts | 24 ++++++++++++++++++++++++ 2 files changed, 31 insertions(+) diff --git a/src/app/compare/[slug]/page.tsx b/src/app/compare/[slug]/page.tsx index 1ef9c90e..8bd07116 100644 --- a/src/app/compare/[slug]/page.tsx +++ b/src/app/compare/[slug]/page.tsx @@ -84,9 +84,16 @@ function parseAdHocSlug(slug: string): { a: string; b: string } | null { * Returns a synthetic ComparePair so the rest of the route works * unchanged. Returns null when any of those checks fail. */ +// HL builder addresses (0x...) leak into the provider catalog because +// the HL bench tracks builders by raw on-chain address. They have no +// search demand as compare targets, only pollute crawl budget. +// Drop them at the entry so /compare/0x...-vs-* 404s cleanly. +const HEX_SLUG_RE = /^0x[0-9a-f]{4,}$/i; + async function resolveAdHocPair(slug: string): Promise { const parsed = parseAdHocSlug(slug); if (!parsed) return null; + if (HEX_SLUG_RE.test(parsed.a) || HEX_SLUG_RE.test(parsed.b)) return null; const [first, second] = [parsed.a, parsed.b].sort(); if (slug !== `${first}-vs-${second}`) return null; const [a, b] = await Promise.all([ diff --git a/src/app/sitemap.ts b/src/app/sitemap.ts index 690c98da..a6bc3535 100644 --- a/src/app/sitemap.ts +++ b/src/app/sitemap.ts @@ -2,6 +2,7 @@ import { statSync } from "node:fs"; import path from "node:path"; import type { MetadataRoute } from "next"; import { getBenchmarks } from "@/data/benchmarks"; +import { COMPARE_PAIRS } from "@/data/compare-pairs"; import { loadAllAlternatives } from "@/lib/alternatives"; import { loadAllAnswers } from "@/lib/answers"; import { CHAIN_BY_SLUG, CHAINS, getBenchmarksForChain } from "@/lib/chains"; @@ -286,6 +287,28 @@ async function buildFullSitemap(): Promise { ) ).filter((r): r is NonNullable => r !== null); + // Curated compare pairs. Both providers are pre-validated to exist + // and share at least one bench (criteria documented in + // src/data/compare-pairs.ts). Defensive recheck via getProvider to + // skip any pair whose provider was removed since publication. + const compareRoutes: MetadataRoute.Sitemap = ( + await Promise.all( + COMPARE_PAIRS.map(async (pair) => { + const [a, b] = await Promise.all([ + getProvider(pair.providerA), + getProvider(pair.providerB), + ]); + if (!a || !b) return null; + return { + url: `${SITE.url}/compare/${pair.slug}`, + lastModified: catalogTs, + changeFrequency: "weekly" as const, + priority: 0.7, + }; + }), + ) + ).filter((r): r is NonNullable => r !== null); + return [ ...staticRoutes, ...benchmarkRoutes, @@ -293,6 +316,7 @@ async function buildFullSitemap(): Promise { ...alternativeRoutes, ...answerRoutes, ...chainRoutes, + ...compareRoutes, ]; }