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
30 changes: 28 additions & 2 deletions src/app/api/citable/route.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,33 @@
import { NextResponse } from "next/server";
import { getBenchmarks } from "@/data/benchmarks";
import { SITE } from "@/data/site";
import { AllBenchmarksDraftError } from "@/lib/spec";
import { citeBundle, fieldValue, leader, headlineSentence } from "@/lib/citation";
import { clientKey, rateLimit, tooManyRequests } from "@/lib/rate-limit";

export const runtime = "nodejs";
export const revalidate = 60;

/** Short 503 with a Retry-After hint, served when the aggregator has
* no live snapshot to surface (Prom blackout + cold KV). Beats serving
* an all-draft index that downstream LLM agents would treat as truth. */
function unavailable(): NextResponse {
return NextResponse.json(
{ error: "benchmarks_unavailable", retryAfterSec: 60 },
{
status: 503,
headers: {
"cache-control": "no-store",
"retry-after": "60",
"access-control-allow-origin": "*",
},
},
);
}

/**
* Flat machine-readable index of every citable benchmark. Designed to be
* the **first** endpoint an AI agent or journalist crawls - gives them
* the **first** endpoint an AI agent or journalist crawls. Gives them
* everything they need to decide whether to deep-link to a specific bench.
*
* License is intentionally surfaced per-row so downstream agents can
Expand All @@ -19,7 +37,15 @@ export async function GET(req: Request) {
const r = rateLimit(clientKey(req, "citable"), 60, 60);
if (!r.ok) return tooManyRequests(r.retryAfterSec);

const benches = (await getBenchmarks()).filter((b) => b.editorialStatus === "live");
let benches;
try {
benches = (await getBenchmarks()).filter(
(b) => b.editorialStatus === "live",
);
} catch (err) {
if (err instanceof AllBenchmarksDraftError) return unavailable();
throw err;
}
const data = benches.map((b) => {
const top = leader(b);
return {
Expand Down
21 changes: 20 additions & 1 deletion src/app/api/llm-context/route.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { getBenchmarks } from "@/data/benchmarks";
import { SITE } from "@/data/site";
import { AllBenchmarksDraftError } from "@/lib/spec";
import { fmtUnit } from "@/lib/format";
import { fieldValue, headlineSentence, leader } from "@/lib/citation";
import { clientKey, rateLimit, tooManyRequests } from "@/lib/rate-limit";
Expand All @@ -26,7 +27,25 @@ export async function GET(req: Request) {
return new Response(await tooMany.text(), { status: tooMany.status, headers: tooMany.headers });
}

const benches = (await getBenchmarks()).filter((b) => b.editorialStatus === "live");
let benches;
try {
benches = (await getBenchmarks()).filter(
(b) => b.editorialStatus === "live",
);
} catch (err) {
if (err instanceof AllBenchmarksDraftError) {
return new Response("benchmarks_unavailable\n", {
status: 503,
headers: {
"content-type": "text/plain; charset=utf-8",
"cache-control": "no-store",
"retry-after": "60",
"access-control-allow-origin": "*",
},
});
}
throw err;
}
const now = new Date().toISOString();

const lines: string[] = [];
Expand Down
4 changes: 2 additions & 2 deletions src/app/benchmarks/[slug]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import Link from "next/link";
import { ArrowUpRight, ChevronDown } from "lucide-react";
import { BackLink } from "@/components/back-link";
import { nonAllValues } from "@/lib/dimensions";
import { getBenchmark, getBenchmarks } from "@/data/benchmarks";
import { getBenchmark, getBenchmarksSafe } from "@/data/benchmarks";
import { Pill } from "@/components/pill";
import { BenchmarkBody } from "@/components/benchmark-body";
import { BenchmarkBodySkeleton } from "@/components/benchmark-body-skeleton";
Expand Down Expand Up @@ -150,7 +150,7 @@ export default async function BenchmarkPage({
// /api/bench/[slug]/variant when a tab is flipped (per-variant
// unstable_cache keeps that at one cheap Prom roundtrip per 60 s
// across all users), and renders the aggregate while it loads.
const all = await getBenchmarks();
const all = await getBenchmarksSafe();
// Seed ONLY the unfiltered key. Seeding the initially-selected
// chain/region/kind combo with the aggregate made the client believe
// it already had that variant, so it never fetched the real one: the
Expand Down
4 changes: 2 additions & 2 deletions src/app/benchmarks/category/[cat]/page.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { Metadata } from "next";
import { notFound } from "next/navigation";
import { getBenchmarks } from "@/data/benchmarks";
import { getBenchmarksSafe } from "@/data/benchmarks";
import { BenchmarkGrid } from "@/components/benchmark-grid";
import { Breadcrumb } from "@/components/breadcrumb";
import { safeJsonLd, buildItemListJsonLd } from "@/lib/jsonld";
Expand Down Expand Up @@ -57,7 +57,7 @@ export default async function BenchmarkCategoryPage({
const entry = CATEGORY_BY_SLUG.get(cat);
if (!entry) notFound();

const all = await getBenchmarks();
const all = await getBenchmarksSafe();
const benchmarks = all.filter((b) => b.category === entry.label);
// Empty-category guard: a category in the enum that has no live bench
// yet returns 404 so the crawler doesn't land on a thin page. The
Expand Down
4 changes: 2 additions & 2 deletions src/app/benchmarks/page.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { Metadata } from "next";
import { getBenchmarks } from "@/data/benchmarks";
import { getBenchmarksSafe } from "@/data/benchmarks";
import { BenchmarkGrid } from "@/components/benchmark-grid";
import { safeJsonLd, buildItemListJsonLd } from "@/lib/jsonld";
import { SITE } from "@/data/site";
Expand All @@ -17,7 +17,7 @@ export const metadata: Metadata = pageMetadata({
});

export default async function BenchmarksPage() {
const benchmarks = await getBenchmarks();
const benchmarks = await getBenchmarksSafe();

// ItemList + BreadcrumbList JSON-LD so search engines and LLMs see the
// page as a structured registry (the data is already in the DOM but
Expand Down
19 changes: 18 additions & 1 deletion src/app/llms.txt/route.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { getBenchmarks } from "@/data/benchmarks";
import { SITE } from "@/data/site";
import { AllBenchmarksDraftError } from "@/lib/spec";
import { headlineSentence } from "@/lib/citation";

export const runtime = "nodejs";
Expand All @@ -13,7 +14,23 @@ export const revalidate = 300;
* https://llmstxt.org for the spec.
*/
export async function GET() {
const benches = await getBenchmarks();
let benches;
try {
benches = await getBenchmarks();
} catch (err) {
if (err instanceof AllBenchmarksDraftError) {
return new Response("benchmarks_unavailable\n", {
status: 503,
headers: {
"content-type": "text/plain; charset=utf-8",
"cache-control": "no-store",
"retry-after": "60",
"access-control-allow-origin": "*",
},
});
}
throw err;
}

const lines: string[] = [];
lines.push(`# OpenChainBench`);
Expand Down
4 changes: 2 additions & 2 deletions src/app/mcp/page.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { Metadata } from "next";
import Link from "next/link";
import { CopyButton } from "@/components/copy-button";
import { getBenchmarks } from "@/data/benchmarks";
import { getBenchmarksSafe } from "@/data/benchmarks";
import { mcpPageLd } from "@/lib/hub-jsonld";
import { safeJsonLd } from "@/lib/jsonld";
import { pageMetadata } from "@/lib/page-metadata";
Expand Down Expand Up @@ -48,7 +48,7 @@ export const metadata: Metadata = pageMetadata({
export const revalidate = 300;

export default async function McpPage() {
const benches = (await getBenchmarks()).filter((b) => b.editorialStatus === "live");
const benches = (await getBenchmarksSafe()).filter((b) => b.editorialStatus === "live");

return (
<article className="mx-auto max-w-3xl px-4 sm:px-6 pt-12 pb-16">
Expand Down
4 changes: 2 additions & 2 deletions src/app/page.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { Metadata } from "next";
import Link from "next/link";
import { ArrowRight } from "lucide-react";
import { getBenchmarks } from "@/data/benchmarks";
import { getBenchmarksSafe } from "@/data/benchmarks";
import { HeroRadar } from "@/components/hero-radar";
import { HomeBenchTable } from "@/components/home-bench-table";
import { LiveDashboard } from "@/components/live/dashboard";
Expand Down Expand Up @@ -32,7 +32,7 @@ export const metadata: Metadata = {
};

export default async function HomePage() {
const benchmarks = await getBenchmarks();
const benchmarks = await getBenchmarksSafe();

return (
<article className="mx-auto max-w-[1400px] px-4 sm:px-6 py-10 sm:py-14 space-y-14 sm:space-y-20">
Expand Down
19 changes: 17 additions & 2 deletions src/app/rss.xml/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
*/

import { NextResponse } from "next/server";
import { loadAllBenchmarks } from "@/lib/spec";
import { AllBenchmarksDraftError, loadAllBenchmarks } from "@/lib/spec";
import { getBenchCreatedAt } from "@/lib/seo/bench-dates";
import { headlineSentence } from "@/lib/citation";
import { SITE } from "@/data/site";
Expand Down Expand Up @@ -59,7 +59,22 @@ function itemDescription(b: Benchmark): string {
}

export async function GET() {
const all = await loadAllBenchmarks();
let all;
try {
all = await loadAllBenchmarks();
} catch (err) {
if (err instanceof AllBenchmarksDraftError) {
return new Response("benchmarks_unavailable\n", {
status: 503,
headers: {
"content-type": "text/plain; charset=utf-8",
"cache-control": "no-store",
"retry-after": "60",
},
});
}
throw err;
}
const live = all.filter((b) => b.editorialStatus === "live");

const items = live
Expand Down
20 changes: 19 additions & 1 deletion src/data/benchmarks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,28 @@ import path from "node:path";
import yaml from "js-yaml";
import { cache } from "react";
import type { Benchmark } from "@/types/benchmark";
import { loadAllBenchmarks, loadBenchmark } from "@/lib/spec";
import {
loadAllBenchmarks,
loadAllBenchmarksSafe,
loadBenchmark,
} from "@/lib/spec";

/**
* Strict loader. Throws AllBenchmarksDraftError when every bench has
* collapsed to draft (Prom blackout, cold start with no KV snapshot).
* Use this in API endpoints, feeds, and crons that should return 503
* rather than poison downstream consumers with an all-draft snapshot.
*/
export const getBenchmarks = cache(loadAllBenchmarks);

/**
* Build-and-render safe loader. Catches the all-draft sentinel and
* returns the draft-placeholder list so `next build` and hub pages
* still render. Use this in pages enumerated by generateStaticParams
* or any UI surface that must always produce HTML.
*/
export const getBenchmarksSafe = cache(loadAllBenchmarksSafe);

export async function getBenchmark(
slug: string,
options: { chain?: string; region?: string } = {}
Expand Down
4 changes: 2 additions & 2 deletions src/lib/chains.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
*/

import { cache } from "react";
import { getBenchmarks } from "@/data/benchmarks";
import { getBenchmarksSafe } from "@/data/benchmarks";
import type { Benchmark } from "@/types/benchmark";

type ChainCategory = "L1" | "L2";
Expand Down Expand Up @@ -225,7 +225,7 @@ export const CHAIN_BY_SLUG = new Map(CHAINS.map((c) => [c.slug, c]));
export const getBenchmarksForChain = cache(async function getBenchmarksForChain(
chainSlug: string,
): Promise<Benchmark[]> {
const benches = await getBenchmarks();
const benches = await getBenchmarksSafe();
return benches.filter((b) => {
if (b.results.some((r) => r.slug === chainSlug)) return true;
if (b.dimensions?.chain?.some((c) => c.value === chainSlug)) return true;
Expand Down
4 changes: 2 additions & 2 deletions src/lib/providers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@

import { cache } from "react";
import { unstable_cache } from "next/cache";
import { getBenchmarks } from "@/data/benchmarks";
import { getBenchmarksSafe } from "@/data/benchmarks";
import { isAll } from "@/lib/dimensions";
import { liveResults } from "@/lib/provider-filters";
import { readBestPerChain } from "@/lib/per-chain-contract";
Expand Down Expand Up @@ -224,7 +224,7 @@ function rankPerChainForBench(
}

async function buildProviders(): Promise<ProviderProfile[]> {
const benches = await getBenchmarks();
const benches = await getBenchmarksSafe();
const byKey = new Map<string, ProviderProfile>();

for (const b of benches) {
Expand Down
Loading
Loading