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
13 changes: 12 additions & 1 deletion src/app/api/og/[slug]/route.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,18 @@ export async function GET(
</div>
</div>
),
size,
{
...size,
// Every social/AI unfurl used to trigger a full satori render
// (max-age=0 → x-vercel-cache MISS on consecutive GETs). The
// underlying data moves every 60s but a 1h-old card is fine for
// an unfurl; s-maxage bounds renders to ~24/day/slug while SWR
// keeps scrapes instant.
headers: {
"cache-control":
"public, s-maxage=3600, stale-while-revalidate=86400",
},
},
);
}

Expand Down
2 changes: 1 addition & 1 deletion src/app/benchmarks/[slug]/opengraph-image.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,6 @@ export default async function OG({
</div>
</div>
),
{ ...size }
{ ...size, headers: { "cache-control": "public, s-maxage=3600, stale-while-revalidate=86400" } }
);
}
2 changes: 1 addition & 1 deletion src/app/benchmarks/[slug]/twitter-image.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,6 @@ export default async function TwitterImage({
</div>
</div>
),
{ ...size }
{ ...size, headers: { "cache-control": "public, s-maxage=3600, stale-while-revalidate=86400" } }
);
}
32 changes: 32 additions & 0 deletions src/app/fee-compare/page.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import type { Metadata } from "next";
import { pageMetadata } from "@/lib/page-metadata";
import { FeeCompareClient } from "@/components/fee-compare-client";
import { buildBreadcrumbJsonLd, safeJsonLd } from "@/lib/jsonld";
import { SITE } from "@/data/site";

export const metadata: Metadata = pageMetadata({
path: "/fee-compare",
Expand All @@ -26,8 +28,38 @@ export default async function FeeComparePage({
const rawDays = parseInt(params.days ?? "90", 10);
const initialDays = isFinite(rawDays) ? Math.min(180, Math.max(7, rawDays)) : 90;

// Only page on the site without JSON-LD until 2026-09: emit the same
// BreadcrumbList shape every hub page ships, plus a WebApplication
// node describing the comparison tool itself.
const jsonLd = {
"@context": "https://schema.org",
"@graph": [
buildBreadcrumbJsonLd([
{ name: "Home", item: SITE.url },
{ name: "Perp DEX fee comparison", item: `${SITE.url}/fee-compare` },
]),
{
"@type": "WebApplication",
"@id": `${SITE.url}/fee-compare#app`,
name: "Perp DEX fee comparison",
url: `${SITE.url}/fee-compare`,
applicationCategory: "FinanceApplication",
operatingSystem: "Web",
offers: { "@type": "Offer", price: "0", priceCurrency: "USD" },
description:
"Compare taker fees between any two perp DEXs. Paste a wallet to see what was actually paid on Hyperliquid or Gains and what the same trades would have cost elsewhere.",
publisher: { "@id": `${SITE.url}/#org` },
},
],
};

return (
<article className="mx-auto max-w-[720px] px-4 sm:px-6 py-10 sm:py-14">
<script
type="application/ld+json"
// biome-ignore lint/security/noDangerouslySetInnerHtml: serialized via safeJsonLd
dangerouslySetInnerHTML={{ __html: safeJsonLd(jsonLd) }}
/>
<p className="font-sans text-[11px] uppercase tracking-[0.18em] text-ink-faint mb-3">
Fee comparison
</p>
Expand Down
41 changes: 40 additions & 1 deletion src/app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,44 @@ import { safeJsonLd } from "@/lib/jsonld";

export const revalidate = 3600;

/**
* Seed the live ticker with a real snapshot at ISR-regeneration time so
* the numbers exist in the cached HTML (most AI crawlers don't run JS
* and used to see dashes). Cost-neutral on Vercel: the fetch runs once
* per Data-Cache window (60s) shared across ALL visitors, never
* per-request — the page stays fully static/ISR. The client WebSocket
* takes over within seconds of hydration.
*/
async function fetchRelayStats(): Promise<
import("@/lib/live/types").GlobalView | null
> {
try {
const res = await fetch("https://stream.openchainbench.com/stats", {
next: { revalidate: 60 },
signal: AbortSignal.timeout(3_000),
});
if (!res.ok) return null;
const d = (await res.json()) as { global?: Record<string, number> };
const g = d.global;
if (!g || typeof g.vol24h !== "number") return null;
return {
vol24h: g.vol24h,
trades24h: g.trades24h ?? 0,
buys24h: g.buys24h ?? 0,
sells24h: g.sells24h ?? 0,
fees24h: g.fees24h ?? 0,
mcap: g.mcap ?? 0,
byChain: [],
lighthouseAt: g.lighthouseAt ?? 0,
mcapAt: g.mcapAt ?? 0,
};
} catch {
// Relay unreachable at regeneration time: render the pre-existing
// dashes rather than fail the page.
return null;
}
}

const DESCRIPTION =
"Live benchmarks for crypto infrastructure: RPC latency, bridge fees, L2 finality and price feed accuracy. Open methodology, updated continuously.";

Expand All @@ -34,6 +72,7 @@ export const metadata: Metadata = {

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

// Same timestamp source the bench pages use for Dataset.dateModified:
// the harness lastRunAt (real data timestamp, not build time). The
Expand Down Expand Up @@ -104,7 +143,7 @@ export default async function HomePage() {
supported chains.
</p>
</header>
<LiveDashboard />
<LiveDashboard initialStats={initialStats} />
</section>

{/* Latest deployed benchmarks */}
Expand Down
2 changes: 1 addition & 1 deletion src/app/reports/[category]/[slug]/opengraph-image.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,6 @@ export default async function OG({
</div>
</div>
),
{ ...size }
{ ...size, headers: { "cache-control": "public, s-maxage=3600, stale-while-revalidate=86400" } }
);
}
12 changes: 10 additions & 2 deletions src/components/live/dashboard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -108,8 +108,16 @@ function emptySeries(): SeriesByRange {
};
}

export function LiveDashboard() {
const [stats, setStats] = useState<GlobalView | null>(null);
export function LiveDashboard({
initialStats = null,
}: {
/** Server-fetched snapshot of the relay's /stats global block, baked
* into the ISR HTML so crawlers (most AI bots don't run JS) see real
* ticker numbers instead of dashes. The WebSocket replaces it within
* seconds of hydration. */
initialStats?: GlobalView | null;
} = {}) {
const [stats, setStats] = useState<GlobalView | null>(initialStats);
const [recent, setRecent] = useState<SwapEvent[]>([]);
const [connected, setConnected] = useState(false);
const [series, setSeries] = useState<SeriesByRange>(emptySeries);
Expand Down
Loading