Guide for adding and updating JSON-LD structured data on web pages. Each schema type includes a complete template — replace {SITE_NAME}, {DOMAIN}, and {SOCIAL_LINKS} with the user's actual values from the site configuration.
Important: Use the site configuration gathered during first-time setup. If not yet gathered, ask the user first per SKILL.md instructions.
- SSR-safe: JSON-LD must NOT be wrapped in browser-only guards (e.g.,
isPlatformBrowserin Angular). It must render during SSG/SSR so search engines see it in the HTML source. - Minimum 2 schemas: Every page needs at least one content schema + BreadcrumbList.
- Single script tag: Combine all schemas into one
<script type="application/ld+json">using a JSON array. - Cleanup on destroy: Always remove the script on component destroy to prevent duplication during SPA navigation.
Use on: Product pages, feature pages, tool pages
const schema = {
'@context': 'https://schema.org',
'@type': 'SoftwareApplication',
'name': '{SITE_NAME} [Product Name]',
'applicationCategory': 'DeveloperApplication',
'operatingSystem': 'Web',
'description': 'Description of the product/feature',
'url': '{DOMAIN}/products/PRODUCT_NAME',
'datePublished': 'YYYY-MM-DD',
'dateModified': 'YYYY-MM-DD',
'author': {
'@type': 'Organization',
'name': '{SITE_NAME}',
'url': '{DOMAIN}'
},
'offers': {
'@type': 'Offer',
'price': '0',
'priceCurrency': 'USD',
'description': 'Free tier available'
},
'featureList': [
'Feature 1',
'Feature 2',
'Feature 3'
]
};Use on: All pages (can be combined with any other schema). Include at minimum on homepage, about, and contact pages.
const schema = {
'@context': 'https://schema.org',
'@type': 'Organization',
'name': '{SITE_NAME}',
'url': '{DOMAIN}',
'logo': '{DOMAIN}/assets/logo.png',
'sameAs': [
// Include the user's social profile URLs here
// e.g., 'https://twitter.com/{handle}',
// 'https://github.com/{org}',
// 'https://linkedin.com/company/{company}'
]
};Use on: Every page (required as one of the minimum 2 schemas).
const schema = {
'@context': 'https://schema.org',
'@type': 'BreadcrumbList',
'itemListElement': [
{
'@type': 'ListItem',
'position': 1,
'name': 'Home',
'item': '{DOMAIN}'
},
{
'@type': 'ListItem',
'position': 2,
'name': 'Section Name',
'item': '{DOMAIN}/section'
},
{
'@type': 'ListItem',
'position': 3,
'name': 'Page Name',
'item': '{DOMAIN}/section/page'
}
]
};Use on: Any page with FAQ content. Produces rich snippets in Google search results (expandable Q&A).
const schema = {
'@context': 'https://schema.org',
'@type': 'FAQPage',
'mainEntity': [
{
'@type': 'Question',
'name': 'Question text here?',
'acceptedAnswer': {
'@type': 'Answer',
'text': 'Plain text answer (strip HTML tags from any rich content)'
}
}
]
};Dynamic FAQ from component data:
const faqSchema = {
'@context': 'https://schema.org',
'@type': 'FAQPage',
'mainEntity': this.faqs.map(faq => ({
'@type': 'Question',
'name': faq.question,
'acceptedAnswer': {
'@type': 'Answer',
'text': faq.answer.replace(/<[^>]*>/g, '') // Strip HTML
}
}))
};Use on: Installation guides, setup pages, getting-started pages.
const schema = {
'@context': 'https://schema.org',
'@type': 'HowTo',
'name': 'How to Set Up [Feature] with {SITE_NAME}',
'description': 'Step-by-step guide to...',
'step': [
{
'@type': 'HowToStep',
'position': 1,
'name': 'Create an Account',
'text': 'Sign up and create your first application.',
'url': '{DOMAIN}/getting-started'
},
{
'@type': 'HowToStep',
'position': 2,
'name': 'Install the SDK',
'text': 'Install the SDK package in your project.',
'url': '{DOMAIN}/docs/installation'
}
],
'totalTime': 'PT5M'
};Use on: Generic pages that don't fit other types (about, contact, legal).
const schema = {
'@context': 'https://schema.org',
'@type': 'WebPage',
'name': 'Page Title',
'description': 'Page description',
'url': '{DOMAIN}/page-path',
'datePublished': 'YYYY-MM-DD',
'dateModified': 'YYYY-MM-DD',
'publisher': {
'@type': 'Organization',
'name': '{SITE_NAME}',
'url': '{DOMAIN}'
}
};Use on: Blog posts, case studies, news articles.
const schema = {
'@context': 'https://schema.org',
'@type': 'Article',
'headline': 'Article Title (max 110 chars)',
'description': 'Article description',
'url': '{DOMAIN}/blog/article-slug',
'datePublished': 'YYYY-MM-DD',
'dateModified': 'YYYY-MM-DD',
'author': {
'@type': 'Organization',
'name': '{SITE_NAME}',
'url': '{DOMAIN}'
},
'publisher': {
'@type': 'Organization',
'name': '{SITE_NAME}',
'url': '{DOMAIN}',
'logo': {
'@type': 'ImageObject',
'url': '{DOMAIN}/assets/logo.png'
}
},
'image': '{DOMAIN}{SOCIAL_IMAGE_PATH}article-og.png'
};Always combine all schemas for a page into a single JSON array:
private addStructuredData() {
const schemas = [contentSchema, breadcrumbSchema, faqSchema]; // etc.
this.jsonLdScript = this.document.createElement('script');
this.jsonLdScript.type = 'application/ld+json';
this.jsonLdScript.text = JSON.stringify(schemas);
this.document.head.appendChild(this.jsonLdScript);
}| Page Type | Primary Schema | Additional Schemas |
|---|---|---|
| Product/feature page | SoftwareApplication | BreadcrumbList, Organization |
| Product page with FAQ | SoftwareApplication | BreadcrumbList, FAQPage |
| Getting started | HowTo | BreadcrumbList, Organization |
| About/company | Organization | BreadcrumbList, WebPage |
| Legal pages | WebPage | BreadcrumbList |
| Blog post | Article | BreadcrumbList, Organization |
| Feature page with FAQ | SoftwareApplication | BreadcrumbList, FAQPage, Organization |
| Homepage | Organization | WebPage |
After implementing, verify structured data appears in prerendered HTML:
grep 'application/ld+json' {BUILD_OUTPUT}/ROUTE_PATH/index.htmlThe JSON-LD should be present in the <head> of the prerendered file. If it's missing, the addStructuredData() method is likely guarded by a browser-only check or not being called during SSR.