Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 35 additions & 14 deletions src/app/sitemap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -171,6 +173,10 @@ async function buildFullSitemap(): Promise<MetadataRoute.Sitemap> {
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 = [
{
Expand Down Expand Up @@ -223,6 +229,15 @@ async function buildFullSitemap(): Promise<MetadataRoute.Sitemap> {
await Promise.all(
providerSlugs.map(async (slug) => {
if (CHAIN_BY_SLUG.has(slug)) return null;
// Tracked Hyperliquid builder frontends live under
// /hyperliquid/<slug>; the /products/<slug> 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/<slug> 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;
}),
Expand Down Expand Up @@ -411,20 +426,26 @@ async function buildFullSitemap(): Promise<MetadataRoute.Sitemap> {
// Per-URL <lastmod> = 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<Date>((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<Date>((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,
Expand Down
6 changes: 5 additions & 1 deletion src/middleware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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",
]);
Expand Down
Loading