Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

History

19 Commits

Repository files navigation

docusaurus-plugin-structured-data

Plugin to configure Structured Data for Docusaurus sites

How it works

This plugin will generate Structured Data for your Docusaurus site, compliant with schema.org.

The plugin will generate the following types of structured data, and include them in the <head> of your site using the JSON-LD format:

  • Organization - augmented using data from themeConfig.structuredData.organization
  • WebSite - augmented using data from themeConfig.structuredData.website
  • WebPage - dynamically generated for each page
  • BreadcrumbList - dynamically generated for each page

Docusaurus generated microdata for BreadcrumbList is removed by this plugin in favor of the corresponding JSON-LD data.

Organization and WebSite can be extended using the themeConfig.structuredData object based upon properties provided (e.g. you can add any schema.org compliant properties for Organization and WebSite and these will be automatically included in your structured data for each page).

WebPage structured data is dynamically generated for each page, and includes the following properties:

BreadcrumbList structured data is dynamically generated for each page based upon the page route.

this plugin uses the postBuild lifecycle hook to generate the structured data for each page, and inject it into the <head> of the page. It is only invoked upon yarn build or npm run build commands being run.

Article structured data is automatically generated by this plugin for blog articles, including calcualting wordCount and including author data.

Installation

NPM

npm i @stackql/docusaurus-plugin-structured-data

YARN

yarn add @stackql/docusaurus-plugin-structured-data

Setup

Add to plugins in docusaurus.config.js:

{plugins: ['@stackql/docusaurus-plugin-structured-data',
...
]}

Update themeConfig in the docusaurus.config.js file, the following shows mandatory properties:

{
...,themeConfig: {structuredData: {excludedRoutes: [],// array of routes to exclude from structured data generation, include custom redirects hereverbose: boolean,// print verbose output to console (default: false)featuredImageDimensions: {width: number,height: number,},authors:{author_name: {authorId: string,// unique id for the author - used as an identifier in structured dataurl: string,// MUST be the same as the `url` property in the `authors.yml` file in the `blog` directoryimageUrl: string,// gravatar urlsameAs: []// synonymous entity links, e.g. github, linkedin, twitter, etc.},},organization: {},// Organization properties can be added to this objectwebsite: {},// WebSite properties can be added to this objectwebpage: {datePublished: string,// default is the current dateinLanguage: string,// default: en-US},breadcrumbLabelMap: {}// used to map the breadcrumb labels to a custom value}},
...
}

Config Example

Below is an example of a docusaurus.config.js file with the themeConfig.structuredData object populated with all available properties:

structuredData: {excludedRoutes: ['/providers',],verbose: true,featuredImageDimensions: {width: 1200,height: 627,},authors:{'Jeffrey Aven': {authorId: '1',url: 'https://www.linkedin.com/in/jeffreyaven/',imageUrl: 'https://s.gravatar.com/avatar/f96573d092470c74be233e1dded5376f?s=80',sameAs: ['https://www.amazon.com/stores/Jeffrey-Aven/author/B0BSP78VVL','https://developers.google.com/community/experts/directory/profile/profile-jeffrey-aven','https://www.linkedin.com/in/jeffreyaven/','https://www.crunchbase.com/person/jeffrey-aven','https://github.com/jeffreyaven','https://dev.to/jeffreyaven',],},},organization: {sameAs: ['https://twitter.com/stackql','https://www.linkedin.com/company/stackql','https://github.com/stackql','https://www.youtube.com/@stackql','https://hub.docker.com/u/stackql',],contactPoint: {'@type': 'ContactPoint',email: 'info@stackql.io',},logo: {'@type': 'ImageObject',inLanguage: 'en-US','@id': 'https://stackql.io/#logo',url: 'https://stackql.io/img/stackql-cover.png',contentUrl: 'https://stackql.io/img/stackql-cover.png',width: 1440,height: 900,caption: 'StackQL - your cloud using SQL',},address: {'@type': 'PostalAddress',addressCountry: 'AU',// https://en.wikipedia.org/wiki/ISO_3166-1postalCode: '3001',streetAddress: 'Level 24, 570 Bourke Street, Melbourne, Victoria',},taxID: 'ABN 65 656 147 054',},website: {inLanguage: 'en-US',},webpage: {inLanguage: 'en-US',datePublished: '2021-07-01',},breadcrumbLabelMap: {'developers': 'Developers','functions': 'Functions','aggregate': 'Aggregate','datetime': 'Date Time','json': 'JSON','math': 'Math','string': 'String','command-line-usage': 'Command Line Usage','getting-started': 'Getting Started','language-spec': 'Language Specification','re': 'Regular Expressions',}},

If your organization is a LocalBusiness or one of its subtypes (e.g. Store, Restaurant), set '@type': 'LocalBusiness' (or the specific subtype) inside your organization config block. Properties like duns, currenciesAccepted, paymentAccepted, and priceRange are defined on LocalBusiness (not on Organization) and only become schema.org-valid once @type is narrowed. taxID is valid on Organization itself and works without changing @type.

AEO / Answer Engine Optimization

As of 1.4.0 this plugin emits structured data that AI search surfaces (Google AI Overviews, Perplexity, ChatGPT search, Claude web tools) consume in addition to the classic SEO entities. All AEO features are opt-in except speakable, which is added to every WebPage node by default and can be turned off globally or per-page.

What each schema is for

SchemaWhat it doesWhere it shows up
FAQPageMarks a list of question/answer pairs as the page's main contentAI Overviews answer cards, Perplexity citations, voice answer extraction
HowToMarks a sequence of steps to complete a taskAI Overviews step-by-step cards, voice walkthroughs
TechArticleSame as Article but signals a technical/documentation contextBetter grounding for ChatGPT / Claude search when answering API or tooling questions
SoftwareApplicationIdentifies a page as describing an installable tool / CLI / libraryKnowledge-panel-style cards in AI surfaces, version / OS metadata for install pages
SpeakableSpecificationTells voice assistants which parts of the page to read aloudGoogle Assistant, smart-speaker answer extraction
mainEntity linkingConnects the primary entity (Article / TechArticle / WebPage) to the secondary entity (FAQPage / HowTo / SoftwareApplication) in a single @graphLets crawlers treat the page as one coherent unit instead of disconnected nodes

How frontmatter reaches the plugin

As of 1.5.0 the plugin reads page frontmatter directly via Docusaurus's allContentLoaded lifecycle hook. The plugin captures frontMatter for every doc, blog post, and MDX page (keyed by permalink) and consults it during postBuild when emitting structured data for each route. No MDX or theme changes are required - if your page has a frontmatter block, the plugin sees it.

Two alternative delivery paths from 1.4.x remain supported and continue to work alongside the frontmatter path:

  • <script type="application/json" data-aeo-faq> / data-aeo-howto / data-aeo-software blocks injected via MDX, for cases where editing frontmatter is awkward (e.g. programmatically generated MDX, pages outside the content-docs / content-blog plugins).
  • <meta name="aeo:proficiencyLevel">, <meta name="aeo:dependencies">, <meta name="aeo:speakable" content="false"> set via a page-level <Head> component.

When frontmatter and the legacy path both declare the same schema for the same page, frontmatter wins and the plugin emits a verbose log entry naming the duplicate.

Configuring TechArticle route prefixes

By default, routes under /docs/* emit TechArticle (in place of generic Article). To extend this to additional surfaces - for example, a curated /ai/* content tree intended for AI retrieval - set techArticleRoutePrefixes in themeConfig.structuredData:

themeConfig: {structuredData: {techArticleRoutePrefixes: ['/docs/','/ai/'],// ...}}

Rules:

  • Each prefix must start and end with /. A misshaped prefix throws at plugin construction time with a clear error.
  • A route matches a prefix iff route.startsWith(prefix). A landing route like /docs or /ai (no trailing slash) does not match /docs/ or /ai/ and stays a plain WebPage. This matches 1.4.x semantics for the docs landing page.
  • Setting an empty array (techArticleRoutePrefixes: []) opts out of TechArticle entirely. Pages under /docs/* will then emit no article node unless their og:type is article, in which case they emit generic Article.
  • Unset means default (['/docs/']).

Frontmatter-driven structured data

This is the preferred path as of 1.5.0. Declare the AEO fields in the normal Markdown / MDX frontmatter block at the top of your page:

---
title: What is StackQL?description: StackQL is a SQL query runtime for cloud and SaaS APIs.proficiencyLevel: Beginnerdependencies: stackql >= 0.6faq:
- question: Is StackQL a database?answer: No. StackQL is a query runtime that translates SQL into provider API calls.
- question: Does StackQL replace Terraform?answer: Not directly. StackQL is for querying and mutating cloud state; Terraform is for declaring desired state.howTo:
name: Install StackQL on macOStotalTime: PT2Msteps:
- name: Install via Homebrewtext: Run "brew install stackql" in a terminal.
- name: Verifytext: Run "stackql --version" and confirm the version prints.softwareApplication:
applicationCategory: DeveloperApplicationoperatingSystem: macOS, Linux, WindowsfeatureList:
- SQL queries against cloud and SaaS providers
- Embeddable in CI pipelinesspeakable:
cssSelector:
- h1
- .lead
- "[data-speakable]"
---

Field reference:

Frontmatter fieldTypeEffect
faqarray of {question, answer}Emits FAQPage node, links from primary entity via mainEntity
howTo{name, totalTime?, estimatedCost?, description?, steps: [{name, text, url?, image?}]}Emits HowTo node
softwareApplicationtrue or object with applicationCategory?, applicationSubCategory?, operatingSystem?, featureList?, softwareVersion?, downloadUrl?, offers?Emits SoftwareApplication node
proficiencyLevel"Beginner" | "Intermediate" | "Expert"Added to TechArticle node (only fires on TechArticle routes)
dependenciesstringAdded to TechArticle node
speakablefalse or {cssSelector?, xpath?}false opts the page out; object overrides the default selectors

softwareApplication: true is the bare opt-in idiom - emits a SoftwareApplication node with just the page title as the name, useful when all the metadata lives elsewhere or you only need the schema marker.

The previously-documented <script type='application/json'> and <meta name='aeo:...'> paths remain supported; use whichever fits your workflow. Frontmatter is the cleaner of the two for net-new content.

Legacy: in-MDX <script> block path

Equivalent to the frontmatter path above but injected at the MDX level instead of declared in the frontmatter block. Use this when frontmatter is not available or convenient.

FAQPage (in an MDX page):

<scripttype="application/json"data-aeo-faq>
{`[ { "question": "How do I install stackql?", "answer": "Run brew install stackql on macOS, or download the latest release from GitHub for Linux and Windows." }, { "question": "Does stackql require a database?", "answer": "No. stackql uses an embedded SQL engine by default and does not require any external database." }]`}
</script>

(The {...} wrapper is MDX-required to pass the JSON string through verbatim without MDX trying to interpret the curly braces.)

HowTo:

<scripttype="application/json"data-aeo-howto>
{`{ "name": "Install stackql on macOS", "totalTime": "PT2M", "steps": [ { "name": "Install via Homebrew", "text": "Run 'brew install stackql' in a terminal." }, { "name": "Verify the install", "text": "Run 'stackql --version' and confirm the version prints." } ]}`}
</script>

TechArticle (/docs/* routes, automatic - no payload needed). Optional metadata via head tags in docusaurus.config.js or a page-level <Head>:

importHeadfrom'@docusaurus/Head';
<Head>
<metaname="aeo:proficiencyLevel"content="Intermediate" />
<metaname="aeo:dependencies"content="stackql >= 0.6, Python 3.10" />
</Head>

Valid proficiencyLevel values per schema.org: Beginner, Intermediate, Expert.

SoftwareApplication:

<scripttype="application/json"data-aeo-software>
{`{ "name": "stackql", "applicationCategory": "DeveloperApplication", "applicationSubCategory": "CLI", "operatingSystem": "macOS, Linux, Windows", "softwareVersion": "0.7.0", "downloadUrl": "https://github.com/stackql/stackql/releases/latest", "featureList": [ "SQL queries against cloud and SaaS providers", "Provider-agnostic IaC introspection", "Embeddable in CI pipelines" ]}`}
</script>

SpeakableSpecification - emitted by default on every WebPage node with selectors ["h1", "article p:first-of-type", "[data-speakable]"]. Override globally:

themeConfig: {structuredData: {speakable: {cssSelector: ['h1','.lead','[data-speakable]'],},// ...}}

Or with xpath:

speakable: {xpath: ['/html/head/title',"//*[@data-speakable]"],}

Opt out globally with speakable: false, or per-page with a head tag:

<Head>
<metaname="aeo:speakable"content="false" />
</Head>

Graph linking

When a page emits a secondary entity (FAQPage, HowTo, or SoftwareApplication), the primary entity's mainEntity is set to the secondary's @id. Priority when more than one secondary is present: HowTo -> FAQPage -> SoftwareApplication. For /docs/* and /blog/* the primary is the article (TechArticle or Article). For non-article routes the primary is the WebPage node.

Worked example

A hypothetical /docs/install/macos page declaring both an FAQ and a SoftwareApplication payload, with the default speakable and the automatic TechArticle classification, produces a @graph of the following shape (abbreviated for clarity):

{
"@context": "https://schema.org",
"@graph": [
{
"@type": "TechArticle",
"@id": "https://stackql.io/docs/install/macos/#article",
"isPartOf": { "@type": "WebPage", "@id": "https://stackql.io/docs/install/macos/#webpage" },
"headline": "Install stackql on macOS",
"mainEntityOfPage": { "@id": "https://stackql.io/docs/install/macos/#webpage" },
"mainEntity": { "@id": "https://stackql.io/docs/install/macos/#faq" },
"proficiencyLevel": "Beginner",
"dependencies": "Homebrew",
"image": { "@id": "https://stackql.io/docs/install/macos/#primaryimage" },
"publisher": { "@id": "https://stackql.io/#organization" },
"articleSection": ["Documentation"],
"inLanguage": "en-US"
},
{
"@type": "WebPage",
"@id": "https://stackql.io/docs/install/macos/#webpage",
"url": "https://stackql.io/docs/install/macos",
"isPartOf": { "@id": "https://stackql.io/#website" },
"breadcrumb": { "@id": "https://stackql.io/docs/install/macos/#breadcrumb" },
"speakable": {
"@type": "SpeakableSpecification",
"cssSelector": ["h1", "article p:first-of-type", "[data-speakable]"]
}
},
{ "@type": "ImageObject", "@id": "https://stackql.io/docs/install/macos/#primaryimage", "...": "..." },
{
"@type": "BreadcrumbList",
"@id": "https://stackql.io/docs/install/macos/#breadcrumb",
"itemListElement": [
{ "@type": "ListItem", "position": 1, "name": "Home", "item": "https://stackql.io" },
{ "@type": "ListItem", "position": 2, "name": "Documentation", "item": "https://stackql.io/docs" },
{ "@type": "ListItem", "position": 3, "name": "install - Install stackql on macOS" }
]
},
{ "@type": "WebSite", "@id": "https://stackql.io/#website", "...": "..." },
{ "@type": "Organization", "@id": "https://stackql.io/#organization", "...": "..." },
{
"@type": "FAQPage",
"@id": "https://stackql.io/docs/install/macos/#faq",
"mainEntity": [
{
"@type": "Question",
"@id": "https://stackql.io/docs/install/macos/#faq-q-0",
"name": "How do I install stackql?",
"acceptedAnswer": { "@type": "Answer", "text": "Run brew install stackql ..." }
}
]
},
{
"@type": "SoftwareApplication",
"@id": "https://stackql.io/docs/install/macos/#softwareapplication",
"name": "stackql",
"applicationCategory": "DeveloperApplication",
"operatingSystem": "macOS, Linux, Windows",
"softwareVersion": "0.7.0",
"featureList": ["SQL queries against cloud and SaaS providers", "..."]
}
]
}

Note the @id chain: TechArticle.mainEntity -> FAQPage.@id, TechArticle.isPartOf -> WebPage.@id, WebPage.breadcrumb -> BreadcrumbList.@id, WebPage.isPartOf -> WebSite.@id, WebSite.publisher -> Organization.@id. Every link resolves inside the same @graph.

Validation

Malformed faq, howTo, or softwareApplication payloads throw at build time with the route, field path, and expected shape - for example:

[docusaurus-plugin-structured-data] route "/docs/install/macos": faq[1].answer must be a non-empty string

so you find out at yarn build rather than at Google's Rich Results Test.

About

Adds `JSON-LD` Structured Data (from `schema.org`) to each generated page in a Docusaurus site

Resources

Stars

16 stars

Watchers

2 watching

Forks

Releases

Packages

Used by

Contributors

Languages