FAQ SSOT — render visible <dl> from same `faq:` frontmatter that drives FAQPage JSON-LD
What was done
Added a single section to src/components/FaqArticle.astro, rendered between the markdown <Content /> and the article CTA, that maps data.faq (the faq: frontmatter array, which is also the source for the FAQPage JSON-LD emitted in <head>):
{faqSchema && (
<section class="article-faq" id="article-faq" aria-labelledby="article-faq-heading">
<h2 id="article-faq-heading">Frequently asked questions</h2>
<dl class="article-faq-list">
{data.faq!.map(pair => (
<>
<dt>{pair.question}</dt>
<dd>{pair.answer}</dd>
</>
))}
</dl>
</section>
)}
CSS for the section mirrors the .methodology-faq (seo-plan) and .entity-faq (FAQ hub) patterns that already exist on the site — same visual treatment for the same logical structure across the three pages that emit FAQPage schema.
The faqSchema boolean check guards the section so articles WITHOUT a faq: frontmatter array (the 5 stub-only articles: code-comprehensive-plan-research, commercial-industrial-site-design, master-planned-communities, multifamily-site-design, overall-project-management) render unchanged.
Why
Per Google’s FAQPage guidelines, every Q&A pair declared in the JSON-LD must be visible somewhere in the page body. Previously, the visible Q&A content lived in markdown body sections (### How much does X cost...) authored by hand to mirror the faq: frontmatter — meaning two copies of the same text in the same file. Easy to drift out of sync, and any fix to one had to be remembered for the other.
Now the visible content is rendered from the same data.faq array the JSON-LD reads. One edit, two outputs in lockstep. This is the same SSOT pattern already running on /dev-iq/faqs/ (entity-defining FAQ hub) and /dev-iq/seo-plan/ (methodology FAQ) — just templated for individual FAQ articles.
Verification
npm run buildclean- All 11 articles with
faq:frontmatter render the new<dl>(verified via grep ondist/dev-iq/faqs/<slug>/index.htmlforarticle-faq-list):- agency-coordination, cost, entitlements, irrigation-systems, neighborhood-meetings, preliminary-layout, public-hearings, road-parking-design, stormwater-management, value-engineering, water-sewer-systems
- Spot-check on
cost/: JSON-LDmainEntity[0].acceptedAnswer.textand visible<dd>text are byte-identical - 5 articles without
faq:frontmatter are unchanged (no section rendered)
Validator should now report 15/0/0 (15 schema items, 0 errors, 0 warnings) per the user’s expectation in the test results message.
Known follow-up — duplicate inline content not yet removed
11 articles currently have inline ### Question markdown sections that mirror the faq: frontmatter. Now that the <dl> renders the canonical version, those inline sections are redundant — they show the same Q&A twice on the page.
Counts of inline ### sections per article (some include extra prose-style mini-Q&As beyond the faq: set):
| Article | inline ### sections | faq: pairs |
|---|---|---|
| irrigation-systems.md | 20 | (varies) |
| value-engineering.md | 21 | |
| water-sewer-systems.md | 17 | |
| entitlements.md | 9 | |
| road-parking-design.md | 8 | |
| neighborhood-meetings.md, public-hearings.md | 7 | |
| agency-coordination.md, preliminary-layout.md, stormwater-management.md | 6 | |
| cost.md | 5 |
This is per-article content cleanup — out of scope for the SSOT validator fix. Each article author should decide which ### sections truly duplicate the faq: content (delete) vs. which are unique narrative content that just happens to be Q&A-formatted (keep). Tracked separately; SSOT compliance does not depend on it.
Files affected
src/components/FaqArticle.astro— added the<dl>rendering block + CSS for.article-faqand.article-faq-listsrc/content/devlog/2026-04-28-faq-ssot-via-dl-from-frontmatter.md— this entry