Schema is the one thing on your page the buyer never sees and the crawler reads first. Get it wrong and you get under-cited without ever knowing why; get it right on all eleven page types and the clarity compounds into citations.
Structured data is the first of the five AI-native properties from AI-Native Website in 4 Weeks. The page-type model says what each page is for; this checklist says what JSON-LD each page emits. Three reasons it matters: schema must be generated, not hand-authored, or it drifts the first time your catalog changes; AI answer engines (Perplexity, AI Overviews, ChatGPT search, Gemini) read structured data to disambiguate intent, and complete schema gets cited more than partial; and rich-result eligibility carries real organic-CTR lift — Glenn Gabe's rich-results case studies are full of it on properly marked pages.
The eleven page types — schema bundle per type
The mapping is canonical and small. Each row is a copy-pasteable checklist for the bundle that page type emits.
| Page type | Schema types emitted | Notes |
|---|---|---|
| home | Organization, BreadcrumbList, FAQPage, WebSite | Organization carries logo, sameAs, contactPoint |
| services_hub | Organization, BreadcrumbList, ItemList (Service nodes) | ItemList enumerates the service detail pages |
| service_detail | Service, BreadcrumbList, FAQPage, AggregateRating (if reviews exist) | Service carries provider, areaServed, serviceType |
| pricing | Service or Product, Offer, BreadcrumbList, FAQPage | Offer carries price, priceCurrency, availability |
| case_studies_hub | Organization, BreadcrumbList, ItemList (Article nodes) | ItemList enumerates case study detail pages |
| case_study_detail | Article, BreadcrumbList, mentions (client Organization), about (Service) | Article author is the operator; client is mentions |
| about | Organization, BreadcrumbList, AboutPage, FAQPage | AboutPage is the page type; Organization is the entity |
| blog_hub | Blog, BreadcrumbList, ItemList (BlogPosting nodes) | Blog carries name, publisher |
| blog_detail | Article, BreadcrumbList, FAQPage, HowTo (if procedural) | Article carries author, datePublished, dateModified |
| pillar | Article, BreadcrumbList, FAQPage, HowTo (if implementation plan present) | Same as blog_detail; distinguished by length + cluster role |
| contact | ContactPage, Organization, BreadcrumbList, ContactPoint | ContactPoint carries contactType, telephone, email |
Worked example — a service-detail page
The service_detail bundle is the most consequential for conversion. Four nodes in a single @graph:
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@graph": [
{
"@type": "Service",
"@id": "https://nobrainermedia.com/services/google-ads-api-integration/#service",
"name": "Google Ads API Integration",
"description": "Server-side conversion attribution via the Google Ads API. Four-week implementation.",
"provider": { "@type": "Organization", "@id": "https://nobrainermedia.com/#organization" },
"serviceType": "Conversion Attribution",
"areaServed": { "@type": "Country", "name": "United States" },
"offers": {
"@type": "Offer",
"priceSpecification": { "@type": "PriceSpecification", "priceCurrency": "USD" }
}
},
{
"@type": "BreadcrumbList",
"itemListElement": [
{ "@type": "ListItem", "position": 1, "name": "Home", "item": "https://nobrainermedia.com/" },
{ "@type": "ListItem", "position": 2, "name": "Services", "item": "https://nobrainermedia.com/services/" },
{ "@type": "ListItem", "position": 3, "name": "Google Ads API Integration", "item": "https://nobrainermedia.com/services/google-ads-api-integration/" }
]
},
{
"@type": "FAQPage",
"mainEntity": [
{
"@type": "Question",
"name": "How long does the implementation take?",
"acceptedAnswer": {
"@type": "Answer",
"text": "Four weeks: milestone map (week 1), Worker scaffold (week 2), platform API wire-up (week 3), launch + verification (week 4)."
}
}
]
},
{
"@type": "Organization",
"@id": "https://nobrainermedia.com/#organization",
"name": "No Brainer Media",
"url": "https://nobrainermedia.com/",
"logo": "https://nobrainermedia.com/logo.svg"
}
]
}
</script>
Three patterns: a single @graph wrapper (cleaner and less fragile than multiple script blocks); @id references between nodes (the Service points at the Organization by @id instead of embedding it, deduplicating the org across the whole site); and an explicit @id on every canonical node (stable URIs like …/#organization give engines a canonical identifier for cross-page entity recognition).
The generator — fed by frontmatter
Generated, never hand-authored. Per-page-type Astro components read frontmatter and emit JSON-LD:
// src/components/schema/ServiceSchema.astro
---
const { service, organization, breadcrumbs, faqs } = Astro.props
const graph = [
{
"@type": "Service",
"@id": `${service.canonical_url}#service`,
name: service.title,
description: service.summary,
provider: { "@type": "Organization", "@id": `${organization.canonical_url}#organization` },
serviceType: service.service_type,
areaServed: { "@type": "Country", name: service.area_served },
},
{
"@type": "BreadcrumbList",
itemListElement: breadcrumbs.map((b, i) => ({
"@type": "ListItem", position: i + 1, name: b.name, item: b.url,
})),
},
faqs && {
"@type": "FAQPage",
mainEntity: faqs.map(f => ({
"@type": "Question", name: f.question,
acceptedAnswer: { "@type": "Answer", text: f.answer },
})),
},
].filter(Boolean)
---
<script type="application/ld+json" set:html={JSON.stringify({ "@context": "https://schema.org", "@graph": graph })} />
About thirty lines per type; eleven generators cover the taxonomy. Each page imports its generator and passes frontmatter; the schema emits at build time and is validated in CI.
Want this schema layer correct on every page from day one? Talk to the team. →
CI validation
Two validators fire before deploy: the Schema.org Validator against every page's HTML, and the Google Rich Results Test against a handful of sampled URLs (its API is rate-limited). Both must pass for the deploy to proceed; locally, pnpm validate:schema runs the Schema.org check without the CI overhead.
The five failure modes
- Missing
@context. Every block needs@context: "https://schema.org". The generator always emits it — never hand-author. - Invalid
@typevalues. Typos like"Servce"or"FAQpage"fail silently. TypeScript types enforce valid values at compile time. - Type mismatches. A JS Date instead of an ISO 8601 string;
"$"instead of"USD"; a phone without country code. A Zod schema validates every frontmatter field at build time. - Missing required properties. Service needs
name+provider; Article needsauthor+headline; Offer needspriceCurrency. The generator throws a build error rather than emit incomplete schema. - Inconsistent
@idacross pages. The Organization@idon a service page must match the one on the home page. Both come from one canonical-URL helper, never per-page interpolation.
Closing
JSON-LD is the first AI-native property because it is the most consequential signal a page sends to crawlers and retrieval systems alike. Eleven page types, eleven generators, two CI validators, all fed from frontmatter so it never drifts — that is the foundation the rest of the stack stands on. The boring stack that prints does not skip schema. Variations away from the pattern above should be justified at code review.
Ready to ship the schema layer correctly on every page from day one? Book a 30-minute call →
Get the kit, not just the theory.
We'll send the build checklist behind this post — and the next pillar when it ships. One email, no drip sequence. Unsubscribe in one click.