From a20d17690d9bb1769a49cccbb9d3dfef395f6544 Mon Sep 17 00:00:00 2001 From: Florent Tapponnier Date: Mon, 6 Jul 2026 12:18:38 +0200 Subject: [PATCH] seo(p0): H1 benchmark on products + FAQPage on compare + RSS in head + rich SERP 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. --- 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) }} /> +