From 62d45f645c17416057e603c328fc3c3cda30a711 Mon Sep 17 00:00:00 2001 From: Florent Tapponnier <160007691+Flotapponnier@users.noreply.github.com> Date: Fri, 4 Sep 2026 01:26:30 +0200 Subject: [PATCH] feat(speedtest): dedicated OG/Twitter card with the dial --- src/app/speedtest-rpc/opengraph-image.tsx | 141 ++++++++++++++++++++++ src/app/speedtest-rpc/page.tsx | 15 ++- 2 files changed, 155 insertions(+), 1 deletion(-) create mode 100644 src/app/speedtest-rpc/opengraph-image.tsx diff --git a/src/app/speedtest-rpc/opengraph-image.tsx b/src/app/speedtest-rpc/opengraph-image.tsx new file mode 100644 index 00000000..0caed4dd --- /dev/null +++ b/src/app/speedtest-rpc/opengraph-image.tsx @@ -0,0 +1,141 @@ +import { ImageResponse } from "next/og"; + +export const runtime = "nodejs"; +export const alt = "RPC Speed Test by OpenChainBench"; +export const size = { width: 1200, height: 630 }; +export const contentType = "image/png"; + +// Same dial geometry as the live tool, rendered once for the social +// card. Static content: cache aggressively (content only changes on +// deploy). +const GAUGE_START = -210; +const GAUGE_SWEEP = 240; +const STOPS = [1, 5, 10, 25, 50, 100, 250, 500, 1000]; + +function polar(cx: number, cy: number, r: number, deg: number): [number, number] { + const rad = (deg * Math.PI) / 180; + return [cx + r * Math.cos(rad), cy + r * Math.sin(rad)]; +} +function arcPath(cx: number, cy: number, r: number, a0: number, a1: number): string { + const [x0, y0] = polar(cx, cy, r, a0); + const [x1, y1] = polar(cx, cy, r, a1); + const large = a1 - a0 > 180 ? 1 : 0; + return `M ${x0.toFixed(1)} ${y0.toFixed(1)} A ${r} ${r} 0 ${large} 1 ${x1.toFixed(1)} ${y1.toFixed(1)}`; +} +function msToAngle(ms: number): number { + const t = Math.log(Math.max(1, Math.min(1000, ms))) / Math.log(1000); + return GAUGE_START + t * GAUGE_SWEEP; +} +const GRAD: [number, [number, number, number]][] = [ + [0.0, [16, 185, 129]], + [0.55, [245, 158, 11]], + [1.0, [239, 68, 68]], +]; +function gradColor(t: number): string { + const x = Math.max(0, Math.min(1, t)); + for (let i = 1; i < GRAD.length; i++) { + const [t1, c1] = GRAD[i]; + const [t0, c0] = GRAD[i - 1]; + if (x <= t1) { + const f = (x - t0) / (t1 - t0); + const c = c0.map((v, j) => Math.round(v + (c1[j] - v) * f)); + return `rgb(${c[0]},${c[1]},${c[2]})`; + } + } + return "rgb(239,68,68)"; +} + +export default function OG() { + const READING = 42; // ms, needle in the green + const angle = msToAngle(READING); + const fillT = (angle - GAUGE_START) / GAUGE_SWEEP; + const SEGS = 48; + const segs: { d: string; color: string }[] = []; + for (let i = 0; i < SEGS; i++) { + const t0 = i / SEGS; + if (t0 >= fillT) break; + const t1 = Math.min((i + 1) / SEGS, fillT); + segs.push({ + d: arcPath(210, 210, 160, GAUGE_START + t0 * GAUGE_SWEEP, GAUGE_START + t1 * GAUGE_SWEEP + 0.6), + color: gradColor(t0), + }); + } + const [nx, ny] = polar(210, 210, 128, angle); + + return new ImageResponse( + ( +
+ {/* Dial */} + + + {segs.map((s, i) => ( + + ))} + {STOPS.map((stop) => { + const [x, y] = polar(210, 210, 122, msToAngle(stop)); + return ( + + {stop} + + ); + })} + + + + {READING} + + + ms + + + + {/* Copy */} +
+
+ Free browser tool +
+
+ RPC Speed Test +
+
+ Benchmark any RPC endpoints from your own connection. 87 EVM + chains, keys never leave your browser. +
+
+ openchainbench.com/speedtest-rpc +
+
+
+ ), + { + ...size, + headers: { + "cache-control": "public, s-maxage=86400, stale-while-revalidate=604800", + }, + }, + ); +} diff --git a/src/app/speedtest-rpc/page.tsx b/src/app/speedtest-rpc/page.tsx index 282ef2f5..8c616b88 100644 --- a/src/app/speedtest-rpc/page.tsx +++ b/src/app/speedtest-rpc/page.tsx @@ -31,13 +31,26 @@ const FAQ = [ }, ]; -export const metadata: Metadata = pageMetadata({ +const baseMetadata = pageMetadata({ path: "/speedtest-rpc", title: "RPC Speed Test: benchmark your endpoints from your browser", description: "Free online RPC latency test. Paste any RPC URLs and measure their real speed from your own connection, across 87 EVM chains. No install, no signup, keys never leave your browser.", }); +// pageMetadata pins the generic root card; point socials at the +// dedicated dial card rendered by ./opengraph-image.tsx instead. +const OG_IMAGE = { + url: `${SITE.url}/speedtest-rpc/opengraph-image`, + width: 1200, + height: 630, +}; +export const metadata: Metadata = { + ...baseMetadata, + openGraph: { ...baseMetadata.openGraph, images: [OG_IMAGE] }, + twitter: { ...baseMetadata.twitter, images: [OG_IMAGE.url] }, +}; + export const revalidate = 3600; export default function SpeedtestRpcPage() {