Skip to content
Blume
Esc
↑↓navigate↵open⌘Jpreview
On this page

Structured data

The schema.org JSON-LD every page carries — WebSite, page, and breadcrumb nodes — plus the optional Organization and SoftwareApplication blocks that tell search engines and agents who you are.

Blume emits schema.org JSON-LD in every page’s <head> so search engines understand your content. On by default:

seo: {
  structuredData: true,
}

Each page includes:

  • a WebSite node for site identity,
  • the page itself, with its description, language, and publish date: a WebPage on the homepage, and an article everywhere else — BlogPosting for blog posts, TechArticle for changelog and docs,
  • on an article, a BreadcrumbList built from the navigation trail.

The homepage is the root of the navigation, so each locale’s home (/fr) and the root of a basePath are WebPages too.

URLs are absolute when deployment.site is set. Without it they’re route-relative, and the WebSite node, which needs an absolute identifier, is left out. Pages marked seo.noindex are skipped. When lastModified is on, each page’s date is also emitted as dateModified, and the visible “Last updated” date is marked up as a <time datetime>, so search engines read that date rather than some other date on the page.

Site identity

The WebSite node says a site exists; it doesn’t say what it is or who runs it — which is what AI agents read JSON-LD for before they recommend or cite you. Two optional blocks fill that in, both needing deployment.site (the nodes carry absolute identifiers):

seo: {
  organization: {
    name: "Acme", // defaults to the site title
    email: "hello@acme.com",
    telephone: "+1 555 0100",
    address: { addressLocality: "Sydney", addressCountry: "AU" },
    logo: "/logo.svg",
    sameAs: ["https://github.com/acme", "https://x.com/acme"],
  },
  software: {
    license: "MIT",
    operatingSystem: "Node.js 22+",
    price: 0, // emitted as an Offer; 0 marks it free
    sameAs: ["https://www.npmjs.com/package/acme"],
  },
}

organization adds an Organization node to every page — the WebSite and page nodes cite it as publisher — with the email and telephone as a ContactPoint (contactType defaults to "customer support") and the address as a PostalAddress, the two fields business-verification checks look for. name and url default to the site’s; a root-relative logo is absolutized like any page URL.

software adds a SoftwareApplication node to the homepage: the product’s name and description (defaulting to the site’s), applicationCategory (default "DeveloperApplication"), operating system, license, an Offer when price is set, and the registry or repository URLs in sameAs. Pass software: true to take every default. The organization, when configured, is cited as its publisher.

Shared entity identifiers

When docs live inside another site (example.com/docs), point at the entities that site already defines instead of defining them again:

seo: {
  jsonLd: {
    websiteId: "https://example.com/#website",
    organizationId: "https://example.com/#organization",
  },
}

Every page names websiteId as its isPartOf, and Blume stops emitting its own WebSite node, whose name and URL would describe the docs instead of the site. Set website: true to emit it anyway under that id, or website: false to omit the docs’ own WebSite node without sharing an id. organizationId becomes every page’s publisher. With seo.organization also set, the Organization node uses that id and carries only the fields you configure: the docs’ title and URL don’t stand in for its name and URL. By default, Blume emits the WebSite node and uses the site URL plus #website and #organization. Identifiers must be absolute URLs, so they work without a site URL too.

Page author and publisher

Reference existing entities on every page:

seo: {
  jsonLd: {
    author: { "@id": "https://example.com/#author" },
    publisher: { "@id": "https://example.com/#publisher" },
  },
}

These references apply to the WebPage or article node, and publisher replaces the organization as its publisher. Blume doesn’t define the entities they point at. A page’s own authors front matter wins over author: each one is credited as a Person.

Publication date from Git

Date pages by the commit that added their content file:

seo: {
  datePublished: "git",
}

Front matter date or changelog.date still wins. Git dates the rest, through the same git log as lastModified. Renames are followed only when the content is unchanged, so a new page never inherits the history of a similar one deleted in the same commit. Pages from non-filesystem sources and uncommitted files get no date from git. A shallow clone dates no page and warns instead, since its oldest commit is where the clone was cut. Fetch full history in CI (VERCEL_DEEP_CLONE=true on Vercel, fetch-depth: 0 for actions/checkout).

The resolved Open Graph image is also included as the page node’s image, whether it comes from frontmatter, a site default, or a generated card.

Custom pages

A custom .astro page rendered through PageLayout emits the same graph, so a marketing home page isn’t the one URL without structured data. A custom page has no content file to date, so pass lastModified to give it a dateModified. Pass structuredDataEnabled={config.structuredData} to keep it in sync with this setting, or structuredDataEnabled={false} to turn it off for that page alone.

Last updated on

Was this page helpful?