diff --git a/src/app/benchmarks/category/[cat]/page.tsx b/src/app/benchmarks/category/[cat]/page.tsx index 8f22acbc..f001cc93 100644 --- a/src/app/benchmarks/category/[cat]/page.tsx +++ b/src/app/benchmarks/category/[cat]/page.tsx @@ -1,6 +1,6 @@ import type { Metadata } from "next"; import { notFound } from "next/navigation"; -import { getBenchmarksSafe } from "@/data/benchmarks"; +import { getBenchmarksSafe, toBenchmarkCardData } from "@/data/benchmarks"; import { BenchmarkGrid } from "@/components/benchmark-grid"; import { Breadcrumb } from "@/components/breadcrumb"; import { safeJsonLd, buildItemListJsonLd } from "@/lib/jsonld"; @@ -103,7 +103,7 @@ export default async function BenchmarkCategoryPage({

b.category)))} /> diff --git a/src/app/benchmarks/page.tsx b/src/app/benchmarks/page.tsx index a92d6428..2a5bb6c8 100644 --- a/src/app/benchmarks/page.tsx +++ b/src/app/benchmarks/page.tsx @@ -1,5 +1,5 @@ import type { Metadata } from "next"; -import { getBenchmarksSafe } from "@/data/benchmarks"; +import { getBenchmarksSafe, toBenchmarkCardData } from "@/data/benchmarks"; import { BenchmarkGrid } from "@/components/benchmark-grid"; import { safeJsonLd, buildItemListJsonLd } from "@/lib/jsonld"; import { SITE } from "@/data/site"; @@ -50,7 +50,7 @@ export default async function BenchmarksPage() { {DESCRIPTION}

- + ); } diff --git a/src/components/benchmark-card.tsx b/src/components/benchmark-card.tsx index 8848f0bc..3f0213c9 100644 --- a/src/components/benchmark-card.tsx +++ b/src/components/benchmark-card.tsx @@ -1,5 +1,5 @@ import Link from "next/link"; -import type { Benchmark } from "@/types/benchmark"; +import type { BenchmarkCardData } from "@/data/benchmarks"; import { Hint } from "@/components/hint"; import { MiniChart } from "@/components/mini-chart"; import { CATEGORY_COLOR } from "@/lib/category-colors"; @@ -11,8 +11,12 @@ import { fmtValue, unitSuffix } from "@/lib/format"; * MiniChart preview, and a 3-column footer (providers / samples / updated). * Drafts render the same skeleton with an "Awaiting first run" placeholder * in place of the chart and an em-dash where the headline number would be. + * + * Prop is the slim `BenchmarkCardData` projection, not the full + * Benchmark. Keeps the RSC payload for `/benchmarks` to the fields the + * card actually reads. */ -export function BenchmarkCard({ benchmark }: { benchmark: Benchmark }) { +export function BenchmarkCard({ benchmark }: { benchmark: BenchmarkCardData }) { const b = benchmark; const isDraft = b.status === "draft"; const catColor = CATEGORY_COLOR[b.category] ?? "var(--color-ink-muted)"; diff --git a/src/components/benchmark-grid.tsx b/src/components/benchmark-grid.tsx index cf055535..1a2bc261 100644 --- a/src/components/benchmark-grid.tsx +++ b/src/components/benchmark-grid.tsx @@ -3,7 +3,7 @@ import { useMemo, useState } from "react"; import Link from "next/link"; import { LayoutGrid, List, Search } from "lucide-react"; -import type { Benchmark } from "@/types/benchmark"; +import type { BenchmarkCardData } from "@/data/benchmarks"; import { BenchmarkCard } from "@/components/benchmark-card"; import { categorySlugFromLabel } from "@/lib/categories"; @@ -30,7 +30,7 @@ export function BenchmarkGrid({ lockedCategory = null, allCategories, }: { - benchmarks: Benchmark[]; + benchmarks: BenchmarkCardData[]; /** When set, force the grid to this category. The pill row still * renders so users can jump to other category hubs via the same UI * they had on the /benchmarks root. */ diff --git a/src/components/mini-chart.tsx b/src/components/mini-chart.tsx index b107afc0..6154a60f 100644 --- a/src/components/mini-chart.tsx +++ b/src/components/mini-chart.tsx @@ -1,5 +1,4 @@ import { useMemo } from "react"; -import type { Benchmark } from "@/types/benchmark"; import { buildProviderColors } from "@/lib/series-colors"; /** @@ -7,10 +6,21 @@ import { buildProviderColors } from "@/lib/series-colors"; * full TimeSeriesChart on the bench detail page. every provider gets a * line in their signature color. but stripped of axes, hover and tabs. * Reads `series24h` from the benchmark's `extras` payload. + * + * Structural prop type: accepts the full `Benchmark` (home table) and + * the slim `BenchmarkCardData` projection (hub grid) so the hub doesn't + * need to ship the full Benchmark shape (series7d, series30d, + * metricPanels, editorial copy, ...) per card in the RSC payload. */ +type MiniChartBenchmark = { + results: { slug: string; name: string; ms: { p50: number } }[]; + higherIsBetter: boolean; + extras: { series24h: Record }; +}; + type Props = { - benchmark: Benchmark; + benchmark: MiniChartBenchmark; /** Internal viewBox width used for path math. Visual width is 100% of parent. */ viewBoxWidth?: number; height?: number; diff --git a/src/data/benchmarks.ts b/src/data/benchmarks.ts index c02f4f92..82bbca0f 100644 --- a/src/data/benchmarks.ts +++ b/src/data/benchmarks.ts @@ -16,6 +16,63 @@ import { loadBenchmark, } from "@/lib/spec"; +/** + * Card-shaped projection used by the hub grid and category pages. The + * full Benchmark object carries ~30 fields per bench (extras.series7d, + * series30d, seriesByRegion*, metricPanels, methodology, abstract, faq, + * findings, perChainExplainer, seoIntro, bestPerChain, worstPerChain, + * cellRanks, providersPerChain, ...) that the card never reads. Passing + * the full shape to `` (a client component) baked the + * whole thing into the RSC payload — 3.2 MB of HTML for 37 cards, which + * is what made `/benchmarks` feel like an 8 s page even though the + * server TTFB is sub-second. + * + * Project once at the page boundary so the wire payload only carries + * what the card + the grid's search/filter use. + */ +export type BenchmarkCardData = { + slug: string; + title: string; + subtitle: string; + category: Benchmark["category"]; + status: Benchmark["status"]; + unit: Benchmark["unit"]; + higherIsBetter: boolean; + sampleSize: number; + lastRunAt: string; + metric: string; + results: { + slug: string; + name: string; + ms: { p50: number }; + }[]; + extras: { series24h: Record }; +}; + +/** Strip a Benchmark to the fields the hub card actually renders. Keeps + * `results` in its original sort order so the card's own sort + * (best-leader heuristic) stays correct. */ +export function toBenchmarkCardData(b: Benchmark): BenchmarkCardData { + return { + slug: b.slug, + title: b.title, + subtitle: b.subtitle, + category: b.category, + status: b.status, + unit: b.unit, + higherIsBetter: b.higherIsBetter, + sampleSize: b.sampleSize, + lastRunAt: b.lastRunAt, + metric: b.metric, + results: b.results.map((r) => ({ + slug: r.slug, + name: r.name, + ms: { p50: r.ms.p50 }, + })), + extras: { series24h: b.extras?.series24h ?? {} }, + }; +} + /** * Strict loader. Throws AllBenchmarksDraftError when every bench has * collapsed to draft (Prom blackout, cold start with no KV snapshot).