From 3a52e1c63420ad461e99a51cc36afced4351cded Mon Sep 17 00:00:00 2001 From: Florent Tapponnier Date: Fri, 24 Jul 2026 19:48:58 +0200 Subject: [PATCH] jsonld: add name to FAQPage + Answer (fixes GSC 'Item: N/A' on FAQ nodes) TechArticle.name was fixed but GSC keeps flagging Item:N/A on the same URLs because it validates the WHOLE @graph, and 2 more nodes lack name: - FAQPage (top-level, inherits name from CreativeWork) - Answer (nested in each Question, same inheritance) Both were missing across all bench pages + answer pages + compare pages. Adds: - buildFaqPageJsonLd gains optional pageName arg (default fallback 'Frequently asked questions') - Answer.name = the question text (semantically: this is the answer TO this question) - Callers on bench/answer/compare pages pass a page-specific name - Inline compare-page FAQ block mirrors the shared builder Same one-liner fix pattern as PR #1257 (TechArticle.name) but on FAQPage + Answer. --- src/app/answers/[slug]/page.tsx | 2 +- src/app/benchmarks/[slug]/page.tsx | 7 ++++++- src/app/compare/[slug]/page.tsx | 7 ++++++- src/lib/jsonld.ts | 12 ++++++++++++ 4 files changed, 25 insertions(+), 3 deletions(-) diff --git a/src/app/answers/[slug]/page.tsx b/src/app/answers/[slug]/page.tsx index 395f1e73..f53b22bc 100644 --- a/src/app/answers/[slug]/page.tsx +++ b/src/app/answers/[slug]/page.tsx @@ -227,7 +227,7 @@ export default async function AnswerPage({ ]), ], }; - const faqLd = buildFaqPageJsonLd(faq, url); + const faqLd = buildFaqPageJsonLd(faq, url, null, ans.question); return (
diff --git a/src/app/benchmarks/[slug]/page.tsx b/src/app/benchmarks/[slug]/page.tsx index 0aa548eb..2c957c67 100644 --- a/src/app/benchmarks/[slug]/page.tsx +++ b/src/app/benchmarks/[slug]/page.tsx @@ -425,7 +425,12 @@ export default async function BenchmarkPage({ // don't leak into the SERP snippet. The visible FAQ section below // mirrors every question/answer, satisfying Google's "content visible // on the page" requirement. - const faqJsonLd = buildFaqPageJsonLd(benchmark.faq, benchmarkUrl); + const faqJsonLd = buildFaqPageJsonLd( + benchmark.faq, + benchmarkUrl, + null, + `${benchmark.title} — frequently asked questions`, + ); return (
diff --git a/src/app/compare/[slug]/page.tsx b/src/app/compare/[slug]/page.tsx index aeab9e75..6db7cb9c 100644 --- a/src/app/compare/[slug]/page.tsx +++ b/src/app/compare/[slug]/page.tsx @@ -772,10 +772,15 @@ export default async function ComparePage({ const faqJsonLd = { "@context": "https://schema.org", "@type": "FAQPage", + // Google's Rich Results validator flags missing `name` on the + // FAQPage parent even when Question.name is set. Add here + on + // Answer nodes so the compare page matches the shared FAQ builder + // in src/lib/jsonld.ts. + name: `${a.name} vs ${b.name} — frequently asked questions`, mainEntity: faqEntries.map((e) => ({ "@type": "Question", name: e.q, - acceptedAnswer: { "@type": "Answer", text: e.a }, + acceptedAnswer: { "@type": "Answer", name: e.q, text: e.a }, })), }; diff --git a/src/lib/jsonld.ts b/src/lib/jsonld.ts index e7193da7..88ef6f71 100644 --- a/src/lib/jsonld.ts +++ b/src/lib/jsonld.ts @@ -123,6 +123,7 @@ export function buildFaqPageJsonLd( faq: FaqItem[] | undefined, pageUrl: string, chain?: string | null, + pageName?: string, ): Record | null { if (!faq || faq.length === 0) return null; const suffix = chainIdFragment(chain); @@ -131,11 +132,22 @@ export function buildFaqPageJsonLd( "@context": "https://schema.org", "@type": "FAQPage", "@id": id, + // FAQPage inherits `name` from CreativeWork. Google's Rich Results + // validator flags missing name as "Item: N/A" on the parent node + // even when Question.name is set on each entry. Callers pass a + // page-specific name; fallback preserves a valid string for any + // caller that predates this arg. + name: pageName ?? "Frequently asked questions", mainEntity: faq.map((item) => ({ "@type": "Question", name: stripFaqMarkdown(item.q), acceptedAnswer: { "@type": "Answer", + // Answer inherits `name` from CreativeWork too. Reusing the + // question text as the answer name is semantically accurate + // (this Answer is the answer TO this question) and closes the + // same "missing name" gap on the nested node. + name: stripFaqMarkdown(item.q), text: stripFaqMarkdown(item.a), }, })),