Skip to content

Extend FAQPage schema to docs comparison pages (Terraform, OpenTofu, AWS CDK, Crossplane)#20081

Merged
alexleventer merged 1 commit into
masterfrom
seo/faqpage-schema-docs-comparisons
Jul 6, 2026
Merged

Extend FAQPage schema to docs comparison pages (Terraform, OpenTofu, AWS CDK, Crossplane)#20081
alexleventer merged 1 commit into
masterfrom
seo/faqpage-schema-docs-comparisons

Conversation

@joeduffy

@joeduffy joeduffy commented Jul 4, 2026

Copy link
Copy Markdown
Member

What

Extends the site's existing FAQPage schema auto-detection so type: docs pages get the same supplemental FAQPage entity that type: what-is pages already receive when they have a "Frequently asked questions" section.

Why

layouts/partials/schema/graph-builder.html only called the FAQ collector partial (faq-entity.html) for what-is pages. But faq-entity.html itself is generic — it just scans any page's raw markdown for a ## Frequently asked questions block with ### ...? sub-headings and returns an empty dict if none are found. That meant every docs-type page with a real, already-written FAQ section was silently missing FAQPage structured data, including the four IaC comparison pages:

  • /docs/iac/comparisons/terraform/ — 7 fully-written Q&As, zero schema
  • /docs/iac/comparisons/opentofu/
  • /docs/iac/comparisons/aws-cdk/
  • /docs/iac/comparisons/crossplane/

These comparison pages are Pulumi's top citation-share content in our AI answer-engine tracking (Terraform comparison alone: 48 citations in the last 28 days, more than any other single docs page) and "pulumi vs terraform" is a Tier 1 SEO/AEO keyword (~720 searches/mo). FAQPage schema is the single highest-leverage structured-data type for both Google rich results and LLM citation probability, per our AEO guidelines — so this class of page not having it at all was the biggest gap I could find today.

How

One condition in graph-builder.html: the FAQ supplement now fires for what-is OR docs type pages, guarded to skip pages whose title/URL already mark them as the dedicated FAQ page (those get FAQPage as their main entity already via main-entity.html, so re-adding it here would duplicate the entity in the @graph). No changes to the parsing logic itself — faq-entity.html's existing, already-safe "return empty dict if no questions found" behavior means this is a no-op for the many docs pages that don't have a Q&A section.

SCHEMA.md updated to document the new behavior.

Verification

  • Traced faq-entity.html's question-detection logic against the live content of content/docs/iac/comparisons/terraform/_index.md: its ## Frequently asked questions heading followed by 7 ### ...? sub-headings satisfies the existing (unchanged) H3-ends-in-? detection rule, so all 7 will be picked up.
  • Confirmed no collision with the dedicated /docs/iac/faq/ page, which is explicitly excluded by the new guard.
  • Hugo isn't available in this sandbox to run a full local build, so I did a manual line-by-line trace of the template logic and the target page's markdown instead; happy to have a maintainer run hugo server to double check before merge, or I can iterate further if CI surfaces anything.

🧠 This PR was created by workprentice on behalf of @joeduffy.

The FAQ auto-detection in graph-builder.html only added a supplemental
FAQPage entity for type: what-is pages, even though faq-entity.html
already parses any page's markdown for a Frequently asked questions
section generically. This silently dropped FAQPage structured data
from every type: docs page with a Q&A section, including the four
IaC comparison pages (Terraform, OpenTofu, AWS CDK, Crossplane), which
together are Pulumi's highest citation-share comparison content and a
Tier 1 SEO/AEO target (pulumi vs terraform, 720 mo. searches).

This generalizes the same supplement to type: docs pages, guarded so
the dedicated /docs/iac/faq/ page (which already gets FAQPage as its
main entity via main-entity.html) is excluded to avoid a duplicate
FAQPage entity in its @graph. faq-entity.html already returns an empty
dict when no question-shaped H3s are found, so this is a no-op for the
many docs pages without a FAQ section.

Immediate beneficiaries with existing, well-formed FAQ sections:
- /docs/iac/comparisons/terraform/ (7 Q&As)
- /docs/iac/comparisons/opentofu/
- /docs/iac/comparisons/aws-cdk/
- /docs/iac/comparisons/crossplane/
@github-actions github-actions Bot added review:triaging Claude Triage is currently classifying the PR domain:infra PR touches workflows, scripts, infra, Makefile, or build config review:in-progress Claude review is currently running and removed review:triaging Claude Triage is currently classifying the PR labels Jul 4, 2026
@github-actions

github-actions Bot commented Jul 4, 2026

Copy link
Copy Markdown
Contributor

Pre-merge Review — Last updated 2026-07-04T17:12:06Z

Tip

Summary: This PR extends the JSON-LD schema generator (layouts/partials/schema/graph-builder.html) so that docs-type pages — not just what-is pages — emit a supplemental FAQPage entity in the @graph when they contain a Q&A section, and documents the new behavior in SCHEMA.md. It adds a guard that excludes dedicated FAQ pages (URL/title containing "faq"/"frequently asked") so they don't receive a duplicate FAQPage node, because main-entity.html already returns FAQPage as their main entity. The wrongness that would matter here is malformed or duplicated structured data (search engines reject it) or an exclusion guard that drifts from main-entity.html's FAQ detection. I traced both referenced partials: the guard's condition is a byte-for-byte copy of main-entity.html's FAQ detection, and faq-entity.html returns an empty dict when no question headings exist, so the change is a safe no-op for the many docs pages with no FAQ section. One low-confidence design note below on how broadly FAQPage now emits across the docs corpus.

Review confidence:

Dimension Level Notes
mechanics HIGH Template logic traced against main-entity.html and faq-entity.html; the full Hugo templating build runs in build-and-deploy.yml.
facts HIGH No external factual claims in this diff; the diff comment's internal-consistency claims were verified against the two referenced partials.
Investigation log
  • Cross-sibling reads: 2 of 2 referenced partials read (main-entity.html, faq-entity.html) — the diff comment's exclusion + no-op claims hold
  • External claim verification: not run (no external claims in this diff)
  • Cited-claim spot-checks: not run (no cited claims)
  • Frontmatter sweep: not run (no frontmatter in diff)
  • Temporal-trigger sweep: not run (no trigger words)
  • Code execution: not run (no static/programs/ change)
  • Code-examples checks: not run (no fenced code blocks in content files)
  • Editorial-balance pass: not run (not under content/blog/)
🚨 Outstanding ⚠️ Low-confidence 💡 Pre-existing ✅ Resolved
0 1 0 0

🔍 Verification trail

  • L51 in layouts/partials/schema/graph-builder.html "docs pages now emit a supplemental FAQPage whenever any H2/H3 heading ends in ?" → 🤷 unverifiable (design tradeoff, not a factual claim; verified against faq-entity.html:37-39)

🚨 Outstanding in this PR

No outstanding findings in this PR.

⚠️ Low-confidence

Review each and resolve as appropriate — these don't block the PR.

  • [L51] layouts/partials/schema/graph-builder.html — Extending FAQPage emission from what-is to the full docs corpus widens the trigger considerably. faq-entity.html treats any H2 or H3 heading ending in ? as a question (faq-entity.html:37-39), not only headings inside a dedicated "Frequently asked questions" section — so a docs page with a single rhetorical section heading like ## What is a stack? will now emit FAQPage structured data. Google's structured-data guidelines expect FAQPage markup to correspond to a genuine FAQ; broad over-emission across the large docs corpus is the kind of thing that can draw a structured-data manual action. This mirrors the pre-existing what-is behavior, so it's a deliberate design tradeoff rather than a defect — flagging it for a conscious confirmation that docs pages with incidental question headings are acceptable FAQPage candidates (or that detection should be tightened to a dedicated section). Note also that SCHEMA.md describes the trigger as "H3s ending in ?" but the code also fires on H2 headings ending in ?; consider aligning the wording.

💡 Pre-existing issues in touched files (optional)

No pre-existing issues in touched files.

✅ Resolved since last review

No items resolved since the last review.

📜 Review history

  • 2026-07-04T17:12:06Z — Extends supplemental FAQPage schema from what-is to docs pages; verified the dedicated-FAQ exclusion matches main-entity.html and is a safe no-op — one low-confidence note on FAQPage over-emission across the docs corpus (4bc25bf)

Need a re-review? Want to dispute a finding? Mention @claude and include #update-review.
(For ad-hoc questions or fixes, just @claude — no hashtag.)

@github-actions github-actions Bot added review:no-blockers Claude review completed cleanly; outstanding is empty and removed review:in-progress Claude review is currently running labels Jul 4, 2026
@pulumi-bot

Copy link
Copy Markdown
Collaborator

@pulumi-bot

Copy link
Copy Markdown
Collaborator

Lighthouse Performance Report

Commit: 4bc25bf | Metric definitions

Page Device Score FCP LCP TBT CLS SI
Homepage Mobile 🔴 37 3.5s 12.6s 1226ms 0.001 7.6s
Homepage Desktop 🟡 79 0.7s 1.9s 284ms 0.016 1.2s
Install Pulumi Mobile 🟡 58 5.4s 8.7s 79ms 0.057 7.6s
Install Pulumi Desktop 🟡 89 1.2s 1.8s 0ms 0.012 1.2s
AWS Get Started Mobile 🟡 60 5.1s 8.5s 72ms 0.092 5.1s
AWS Get Started Desktop 🟡 85 1.1s 1.7s 0ms 0.036 2.7s

@joeduffy joeduffy requested a review from alexleventer July 4, 2026 17:31
@alexleventer alexleventer merged commit f8e0976 into master Jul 6, 2026
10 checks passed
@alexleventer alexleventer deleted the seo/faqpage-schema-docs-comparisons branch July 6, 2026 17:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

domain:infra PR touches workflows, scripts, infra, Makefile, or build config review:no-blockers Claude review completed cleanly; outstanding is empty

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants