Structured data
Every build adds structured data — schema.org JSON-LD
— to each public page’s <head>. Search engines and AI answer engines read it
to learn what a page is, where it sits on your site, and who publishes it.
Leave it alone and:
- every page gets a
WebSitenode for your docs site; - every page in
src/content/docs/that is not a splash page also gets aTechArticledescribing the page and aBreadcrumbListof the pages above it.
No company or product is named until you add one. Nothing is guessed.
For a page at /guides/setup/, the build writes:
{ "@context": "https://schema.org", "@graph": [ { "@type": "TechArticle", "@id": "https://docs.example.com/guides/setup/#article", "headline": "Set up your account", "description": "Create an account and invite your team.", "mainEntityOfPage": "https://docs.example.com/guides/setup/", "isPartOf": { "@id": "https://docs.example.com/#website" }, "inLanguage": "en" }, { "@type": "BreadcrumbList", "@id": "https://docs.example.com/guides/setup/#breadcrumb", "itemListElement": [ { "@type": "ListItem", "position": 1, "name": "Home", "item": "https://docs.example.com/" }, { "@type": "ListItem", "position": 2, "name": "Guides", "item": "https://docs.example.com/guides/" }, { "@type": "ListItem", "position": 3, "name": "Set up your account" } ] } ]}Every value comes from the page: its title, its description, and its URL.
You don’t write any of it, and it stays right when you edit the page.
Name your company and product
Section titled “Name your company and product”Fill in organization in src/config/structured-data.mjs:
export const organization = { name: 'Acme', url: 'https://acme.com/', description: 'Acme makes rockets for road runners.', logo: 'https://acme.com/logo.png', sameAs: ['https://www.linkedin.com/company/acme', 'https://github.com/acme'], product: { type: 'SoftwareApplication', name: 'Acme Rocket', description: 'Launches anything, anywhere.', url: 'https://acme.com/rocket/', applicationCategory: 'DeveloperApplication', },};Every page then also gets an Organization node with your logo and a node
for your product, and each page’s TechArticle names your company as its
publisher and your product as what it is about.
The company needs name and url; the product needs type and name.
Leave any other field out and it’s left out of the data. The company’s url,
and the product’s if you set one, must be a full address, https://
included, or the build stops:
src/config/structured-data.mjs: organization.url is "acme.com", but must be an absolute http(s) URLproduct sits inside organization because your company publishes it. Its
identity is its own url plus #product — https://acme.com/rocket/#product
here — so if you run a docs site for each of several products, each names its
own. A product with no url takes your company’s website instead:
https://acme.com/#product. For type, use the schema.org type that fits:
SoftwareApplication, WebApplication, MobileApplication, or Product.
A page’s own block wins
Section titled “A page’s own block wins”If a page already carries JSON-LD of a kind the build generates — in its
frontmatter head, or in the head of astro.config.mjs — that page keeps
its own, and the generated one of that kind is left out:
| Generated | Left out when the page already has |
|---|---|
TechArticle | a TechArticle, Article, BlogPosting or NewsArticle |
BreadcrumbList | a BreadcrumbList |
WebSite, Organization, your product | a node of the same type |
Other types, such as FAQPage, are never affected: write them by hand as
before, and the page gets both. A hand-written block that isn’t valid JSON
counts for nothing.
So a page never carries two articles or two breadcrumbs. But a hand-written block is not updated when you edit the page. Delete the ones the build now writes for you.
How breadcrumbs are named
Section titled “How breadcrumbs are named”A page’s breadcrumb starts at your home page and lists each folder above the
page that has a page of its own — an index.md in that folder — then the page
itself:
- Home: your home page’s
sidebar.label, or itstitle. With no home page, your site’s title. - Each folder with its own page: that page’s
sidebar.label, or itstitle. A folder with no page of its own is skipped. - The page itself: its
title.
To give a section a short name in breadcrumbs, add sidebar.label to its
index page:
---title: Everything you can do with guidessidebar: label: Guides---That is Starlight’s own field, so it also renames the page in the sidebar —
unless your sidebar configuration gives the entry its own label, which wins.
dateModified
Section titled “dateModified”A page’s TechArticle gets a dateModified only when the
sitemap gives the page a <lastmod>, and it is always the same
date. Turn on lastmodFromGit in src/config/sitemap.mjs and both appear
together; leave it off and neither does. A page the sitemap can’t date gets no
dateModified — never the date of the build.
With dates on, if astro.config.mjs sets base, pass it to the sitemap as
well, as the sitemap page shows. Otherwise your
pages get dates the sitemap doesn’t have, and npm test fails, listing each
page whose date disagrees.
Which pages get what
Section titled “Which pages get what”| Page | What it gets |
|---|---|
A page in src/content/docs/ | WebSite, your company and product once set, and its own TechArticle and BreadcrumbList |
| A splash page, such as the home page | WebSite, and your company and product once set |
An API reference page, or any other page outside src/content/docs/ | WebSite, and your company and product once set |
Pages under /private/, and /demo-login | Nothing — the sitemap leaves them out too |
Check that it worked
Section titled “Check that it worked”-
Build the site:
npm run build. -
Count the generated blocks on a docs page:
Terminal window grep -o 'data-generated="structured-data"' dist/client/guides/example/index.html | wc -lA docs page has
2; a splash page has1. On Vercel, the same file is in.vercel/output/static/. -
Once deployed, paste a page’s URL into Google’s Rich Results Test. It lists the breadcrumb Google can show in results. To see every node, use the Schema Markup Validator.
npm test also checks every build: each docs page that gets a generated
article or breadcrumb has exactly one of it (a second usually means a block in
the page body, which the generator cannot see; move it into the frontmatter
head), no generated node repeats an @id already on its page, the generated
blocks are valid JSON, and each dateModified matches the sitemap.
Turn it off
Section titled “Turn it off”export const generateStructuredData = false;Nothing is generated, and the tests that check it are skipped. Blocks you wrote by hand are unaffected.
Settings
Section titled “Settings”All in src/config/structured-data.mjs.
| Setting | Leave it alone and… |
|---|---|
generateStructuredData | true: every page gets its structured data. |
organization | undefined: no company or product is named anywhere. |
organization.product | No product node, and no page says what it is about. |