From 8a5c6146f13bea6da2fb8dea2f0bcad499469b8c Mon Sep 17 00:00:00 2001 From: Flotapponnier <160007691+Flotapponnier@users.noreply.github.com> Date: Wed, 8 Jul 2026 14:00:30 +0200 Subject: [PATCH] fix(sitemap): drop 410'd bench slugs, HL builder /products redirects, empty categories (#980) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Prod deploy sitemap-smoke rolled back on 4 URLs that the sitemap advertises but which return non-200: https://openchainbench.com/benchmarks/bridge-revenue → 410 https://openchainbench.com/benchmarks/evm-quote-latency → 410 https://openchainbench.com/products/phantom-perps → 307 https://openchainbench.com/benchmarks/category/wallets → 404 Root cause for each: 1. bridge-revenue + evm-quote-latency: middleware returns 410 on prod (REMOVED_BENCH_SLUGS) but their YAML still says status:live, so the sitemap generator still emits them. 2. phantom-perps: it's a tracked HL builder, /products/ redirects to /hyperliquid/. The sitemap listed the redirect source instead of the canonical target. 3. wallets category: no bench currently declares category:'Wallets' but the CATEGORIES enum still ships the slug; the page component 404s. Fixes (all in src/app/sitemap.ts): - Import REMOVED_BENCH_SLUGS (exported from middleware.ts) and skip them in benchmarkRoutes.flatMap. - Import isHlBuilderSlug and filter validatedSlugs on it in addition to the existing chain-slug filter. - Re-add the liveCategoryLabels filter on categoryRoutes so an empty CATEGORY entry doesn't produce a URL. Middleware unchanged except REMOVED_BENCH_SLUGS is now exported. Co-authored-by: Florent Tapponnier --- src/app/sitemap.ts | 49 +++++++++++++++++++++++++++++++++------------- src/middleware.ts | 6 +++++- 2 files changed, 40 insertions(+), 15 deletions(-) diff --git a/src/app/sitemap.ts b/src/app/sitemap.ts index 84bee9f5..8f587e2a 100644 --- a/src/app/sitemap.ts +++ b/src/app/sitemap.ts @@ -4,6 +4,8 @@ import type { MetadataRoute } from "next"; import { getBenchmarks } from "@/data/benchmarks"; import { COMPARE_PAIRS } from "@/data/compare-pairs"; import { BRAND_WHITELIST } from "@/lib/compare/brand-whitelist"; +import { REMOVED_BENCH_SLUGS } from "@/middleware"; +import { isHlBuilderSlug } from "@/lib/hl-builder-stats"; import { loadAllAlternatives } from "@/lib/alternatives"; import { loadAllAnswers } from "@/lib/answers"; import { CHAIN_BY_SLUG, CHAINS, getBenchmarksForChain } from "@/lib/chains"; @@ -171,6 +173,10 @@ async function buildFullSitemap(): Promise { const staticRoutes = staticHubRoutes(catalogTs); const benchmarkRoutes: MetadataRoute.Sitemap = benchmarks.flatMap((b) => { + // Middleware returns 410 for these slugs on prod (see middleware.ts). + // Emitting them in the sitemap advertises URLs the middleware will + // then reject, failing the deploy-time sitemap-smoke gate. + if (REMOVED_BENCH_SLUGS.has(b.slug)) return []; const last = b.lastRunAt ? new Date(b.lastRunAt) : BUILD_TIME; const entries: MetadataRoute.Sitemap = [ { @@ -223,6 +229,15 @@ async function buildFullSitemap(): Promise { await Promise.all( providerSlugs.map(async (slug) => { if (CHAIN_BY_SLUG.has(slug)) return null; + // Tracked Hyperliquid builder frontends live under + // /hyperliquid/; the /products/ route 307-redirects + // there for these slugs (products/[slug]/page.tsx). Emitting the + // /products/ variant advertises URLs that immediately redirect, + // and the deploy-time sitemap-smoke gate treats any 3xx as a + // rollback signal. The canonical /hyperliquid/ URLs come + // from hyperliquid/[slug]/generateStaticParams via Next's + // automatic sitemap discovery, not from this file. + if (await isHlBuilderSlug(slug)) return null; const p = await getProvider(slug); return p ? slug : null; }), @@ -411,20 +426,26 @@ async function buildFullSitemap(): Promise { // Per-URL = max lastRunAt of benches in the category. // Benchmark.category is the display label ("Aggregators"), CATEGORIES // entry has both label and slug — match on label. - const categoryRoutes: MetadataRoute.Sitemap = CATEGORIES.map((c) => { - const catBenches = benchmarks.filter((b) => b.category === c.label); - const last = catBenches.reduce((acc, b) => { - if (!b.lastRunAt) return acc; - const t = new Date(b.lastRunAt); - return t > acc ? t : acc; - }, new Date(0)); - return { - url: `${SITE.url}/benchmarks/category/${c.slug}`, - lastModified: last.getTime() > 0 ? last : catalogTs, - changeFrequency: "weekly" as const, - priority: 0.6, - }; - }); + // Empty categories (currently "Wallets") 404 at the page component, + // which fails the sitemap-smoke gate. Filter them here so a category + // lit only by a spec status:draft doesn't leak into the sitemap. + const liveCategoryLabels = new Set(benchmarks.map((b) => b.category)); + const categoryRoutes: MetadataRoute.Sitemap = CATEGORIES + .filter((c) => liveCategoryLabels.has(c.label)) + .map((c) => { + const catBenches = benchmarks.filter((b) => b.category === c.label); + const last = catBenches.reduce((acc, b) => { + if (!b.lastRunAt) return acc; + const t = new Date(b.lastRunAt); + return t > acc ? t : acc; + }, new Date(0)); + return { + url: `${SITE.url}/benchmarks/category/${c.slug}`, + lastModified: last.getTime() > 0 ? last : catalogTs, + changeFrequency: "weekly" as const, + priority: 0.6, + }; + }); return [ ...staticRoutes, diff --git a/src/middleware.ts b/src/middleware.ts index 480e28cd..bfb5e389 100644 --- a/src/middleware.ts +++ b/src/middleware.ts @@ -49,8 +49,12 @@ const CANONICAL_NO_QUERY = new Set([ * removed (or that were never on production but their dev path could * have leaked into the index via staging crawl). 410 on prod, normal * render on dev / preview. + * + * Also re-exported so the sitemap can exclude these routes on prod; + * emitting them advertises URLs that middleware immediately 410s, + * which fails the sitemap-smoke gate and rolls back every deploy. */ -const REMOVED_BENCH_SLUGS = new Set([ +export const REMOVED_BENCH_SLUGS = new Set([ "bridge-revenue", "evm-quote-latency", ]);