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
44 changes: 44 additions & 0 deletions src/app/compare/[slug]/page.tsx
Original file line numberDiff line numberDiff line change
Expand Up@@ -696,6 +696,45 @@ export default async function ComparePage({
]),
};

// FAQPage schema. Google + Bing both render rich FAQ dropdowns in the
// SERP snippet for pages emitting valid FAQPage. Every answer here is
// derived from live measurements — no editorial claim. Skipped when
// shared is empty (never actually reached because notFound() short-
// circuits above, but defensive).
const faqEntries: Array<{ q: string; a: string }> = [];
const aWinsBench = shared.find((s) => s.aggregateWinner === "a" && s.aResult.p50 > 0 && s.bResult.p50 > 0);
const bWinsBench = shared.find((s) => s.aggregateWinner === "b" && s.aResult.p50 > 0 && s.bResult.p50 > 0);
faqEntries.push({
q: `${a.name} vs ${b.name}: which one is better?`,
a: `${a.name} and ${b.name} are compared on ${shared.length} shared OpenChainBench benchmarks. ${aWinsBench ? `${a.name} leads on ${aWinsBench.title}.` : ""} ${bWinsBench ? `${b.name} leads on ${bWinsBench.title}.` : ""} See the live table on this page for every metric.`.trim(),
});
if (aWinsBench) {
faqEntries.push({
q: `Which is faster on ${aWinsBench.title.toLowerCase()}, ${a.name} or ${b.name}?`,
a: `On the ${aWinsBench.title} benchmark, ${a.name} leads with a ${aWinsBench.unit} value that beats ${b.name}. Live measurement is updated continuously by the OpenChainBench harness.`,
});
}
if (bWinsBench) {
faqEntries.push({
q: `Which is faster on ${bWinsBench.title.toLowerCase()}, ${a.name} or ${b.name}?`,
a: `On the ${bWinsBench.title} benchmark, ${b.name} leads with a ${bWinsBench.unit} value that beats ${a.name}. Live measurement is updated continuously by the OpenChainBench harness.`,
});
}
faqEntries.push({
q: `How is the ${a.name} vs ${b.name} comparison measured?`,
a: `Every benchmark on this page uses the same open methodology, published at ${SITE.url}/methodology. Data is CC-BY-4.0. Measurement harnesses are MIT-licensed.`,
});

const faqJsonLd = {
"@context": "https://schema.org",
"@type": "FAQPage",
mainEntity: faqEntries.map((e) => ({
"@type": "Question",
name: e.q,
acceptedAnswer: { "@type": "Answer", text: e.a },
})),
};

const comparisonProse = buildComparisonProse(shared, a.name, b.name);

return (
Expand All@@ -710,6 +749,11 @@ export default async function ComparePage({
// biome-ignore lint/security/noDangerouslySetInnerHtml: serialized via safeJsonLd
dangerouslySetInnerHTML={{ __html: safeJsonLd(breadcrumbJsonLd) }}
/>
<script
type="application/ld+json"
// biome-ignore lint/security/noDangerouslySetInnerHtml: serialized via safeJsonLd
dangerouslySetInnerHTML={{ __html: safeJsonLd(faqJsonLd) }}
/>

<Breadcrumb
items={[
Expand Down
28 changes: 25 additions & 3 deletions src/app/layout.tsx
Original file line numberDiff line numberDiff line change
Expand Up@@ -63,9 +63,20 @@ export const metadata: Metadata = {
},
description:
"Live benchmarks for crypto infrastructure: RPC latency, bridge fees, L2 finality and price feed accuracy. Open methodology, updated continuously.",
...(IS_STAGING && {
robots: { index: false, follow: false, googleBot: { index: false, follow: false } },
}),
robots: IS_STAGING
? { index: false, follow: false, googleBot: { index: false, follow: false } }
: {
index: true,
follow: true,
// Grant full snippet + large image previews so Bing / Google SERP
// stops truncating our data-rich prose and stops suppressing the
// og:image. Default meta robots caps snippet ~155 chars + max-
// image-preview:none (invisible on SERP for AI-scraped queries).
// Next.js Metadata types: camelCase.
"max-snippet": -1,
"max-image-preview": "large",
"max-video-preview": -1,
} as Metadata["robots"],
openGraph: {
title: "OpenChainBench",
description:
Expand All@@ -80,6 +91,17 @@ export const metadata: Metadata = {
description: "Open benchmarks for crypto infrastructure.",
site: SITE.twitter,
},
// <link rel="alternate" type="application/rss+xml"> so feed readers
// + AI crawlers (Perplexity, Bing News, Claude) auto-discover the
// RSS feed. Route: src/app/rss.xml/route.ts.
alternates: {
canonical: SITE.url,
types: {
"application/rss+xml": [
{ url: "/rss.xml", title: "OpenChainBench — new benchmarks" },
],
},
},
};

const ORG_JSONLD = {
Expand Down
2 changes: 1 addition & 1 deletion src/app/products/[slug]/page.tsx
Original file line numberDiff line numberDiff line change
Expand Up@@ -435,7 +435,7 @@ export default async function ProviderPage({
<ProviderLogo slug={p.slug} name={p.name} size={56} />
<div className="min-w-0">
<h1 className="display text-2xl sm:text-3xl md:text-4xl tracking-tight">
{p.name}
{p.name} <span className="text-ink-soft font-normal">Benchmark</span>
</h1>
<p className="mt-1 text-base text-ink-soft">
{productProse}
Expand Down
Loading