From fc31397fac5910151b4bab6231ee64e098f8f042 Mon Sep 17 00:00:00 2001
From: Flotapponnier <160007691+Flotapponnier@users.noreply.github.com>
Date: Mon, 6 Jul 2026 12:20:28 +0200
Subject: [PATCH] seo(p0): H1 benchmark on products + FAQPage on compare + RSS
in head + rich SERP (#916)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Three P0 SEO wins from post-shipping audit (2026-07-05):
1. products/[slug]/page.tsx — H1 now emits '{Provider} Benchmark'
instead of bare '{Provider}'. Matches the SEO title '{Provider}
Benchmark 2026' shipped in #908. Semantic mismatch was killing CTR
(only 1 click observed on 'sui benchmark' at pos 2). Small styled
'Benchmark' suffix preserves visual hierarchy.
2. compare/[slug]/page.tsx — FAQPage JSON-LD. Bing + Google render rich
FAQ dropdowns in SERP for pages emitting valid FAQPage. Every answer
is derived from the shared benchmarks (winner + metric + link to
methodology), so it re-uses data already in DOM — no editorial claim.
3. layout.tsx — RSS feed discovery + rich SERP directives:
-
via Metadata.alternates.types so feed readers, Perplexity, Bing
News, Claude auto-discover the existing /rss.xml route.
- meta robots gains max-snippet:-1 + max-image-preview:large +
max-video-preview:-1 so SERP stops truncating our data-rich prose
and stops suppressing og:image on AI-scraped queries.
Skipped from the audit punch list (deferred to a separate PR):
- Cache-Control s-maxage / stale-while-revalidate on dynamic routes
- next/image migration for WebP/AVIF
- Preconnect hints for analytics.ahrefs.com
The 3 shipped here are the highest-signal / lowest-risk subset.
Co-authored-by: Florent Tapponnier
---
src/app/compare/[slug]/page.tsx | 44 ++++++++++++++++++++++++++++++++
src/app/layout.tsx | 28 +++++++++++++++++---
src/app/products/[slug]/page.tsx | 2 +-
3 files changed, 70 insertions(+), 4 deletions(-)
diff --git a/src/app/compare/[slug]/page.tsx b/src/app/compare/[slug]/page.tsx
index 223a8377..7129e594 100644
--- a/src/app/compare/[slug]/page.tsx
+++ b/src/app/compare/[slug]/page.tsx
@@ -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 (
@@ -710,6 +749,11 @@ export default async function ComparePage({
// biome-ignore lint/security/noDangerouslySetInnerHtml: serialized via safeJsonLd
dangerouslySetInnerHTML={{ __html: safeJsonLd(breadcrumbJsonLd) }}
/>
+
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 = {
diff --git a/src/app/products/[slug]/page.tsx b/src/app/products/[slug]/page.tsx
index 0adc76d6..eecaa93f 100644
--- a/src/app/products/[slug]/page.tsx
+++ b/src/app/products/[slug]/page.tsx
@@ -435,7 +435,7 @@ export default async function ProviderPage({
- {p.name}
+ {p.name} Benchmark
{productProse}