From 916284a2578258c7a625bc2b7be8feb75d7dfab0 Mon Sep 17 00:00:00 2001 From: Erin O'Connell Date: Mon, 9 Nov 2026 11:33:20 -0700 Subject: [PATCH 1/2] Add TechArticle JSON-LD to every docs page --- astro.config.ts | 5 +- src/plugins/techarticle-json-ld-middleware.ts | 53 +++++++++++++++++++ 2 files changed, 57 insertions(+), 1 deletion(-) create mode 100644 src/plugins/techarticle-json-ld-middleware.ts diff --git a/astro.config.ts b/astro.config.ts index 007b6e8b..5c86621b 100644 --- a/astro.config.ts +++ b/astro.config.ts @@ -101,7 +101,10 @@ export default defineConfig({ lang: "en", }, }, - routeMiddleware: "./src/plugins/toc-formatting-middleware.ts", + routeMiddleware: [ + "./src/plugins/toc-formatting-middleware.ts", + "./src/plugins/techarticle-json-ld-middleware.ts", + ], components: { Head: "./src/components/Head.astro", }, diff --git a/src/plugins/techarticle-json-ld-middleware.ts b/src/plugins/techarticle-json-ld-middleware.ts new file mode 100644 index 00000000..1da1acfe --- /dev/null +++ b/src/plugins/techarticle-json-ld-middleware.ts @@ -0,0 +1,53 @@ +import { defineRouteMiddleware } from "@astrojs/starlight/route-data"; + +const ORGANIZATION = { + "@type": "Organization", + name: "System76", + url: "https://system76.com", +}; + +// Adds schema.org TechArticle structured data (JSON-LD) to every docs page. +export const onRequest = defineRouteMiddleware((context) => { + const { starlightRoute } = context.locals; + + // The 404 page isn't an article. + if (starlightRoute.id === "404") { + return; + } + + // Starlight already puts the canonical URL in the head. Reuse it so the URL carries the right + // host and the /tech-docs base on every environment. Without a canonical (no `site` set), skip. + const canonical = starlightRoute.head.find( + (tag) => tag.tag === "link" && tag.attrs?.rel === "canonical", + )?.attrs?.href; + + if (typeof canonical !== "string") { + return; + } + + const { title, description } = starlightRoute.entry.data; + const { lastUpdated } = starlightRoute; + + const jsonLd = { + "@context": "https://schema.org", + "@type": "TechArticle", + headline: title, + description, + url: canonical, + mainEntityOfPage: { "@type": "WebPage", "@id": canonical }, + // Taken from git history when available. There is no publish date to use. + dateModified: lastUpdated?.toISOString(), + inLanguage: starlightRoute.lang, + author: ORGANIZATION, + publisher: ORGANIZATION, + }; + + starlightRoute.head.push({ + tag: "script", + attrs: { type: "application/ld+json" }, + // Drop empty fields, and escape "<" so page text can't close the script tag. + content: JSON.stringify( + Object.fromEntries(Object.entries(jsonLd).filter(([, value]) => value)), + ).replace(/ Date: Mon, 9 Nov 2026 11:36:35 -0700 Subject: [PATCH 2/2] Skip TechArticle JSON-LD on landing pages --- src/plugins/techarticle-json-ld-middleware.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/plugins/techarticle-json-ld-middleware.ts b/src/plugins/techarticle-json-ld-middleware.ts index 1da1acfe..e99673eb 100644 --- a/src/plugins/techarticle-json-ld-middleware.ts +++ b/src/plugins/techarticle-json-ld-middleware.ts @@ -10,8 +10,8 @@ const ORGANIZATION = { export const onRequest = defineRouteMiddleware((context) => { const { starlightRoute } = context.locals; - // The 404 page isn't an article. - if (starlightRoute.id === "404") { + // The 404 page and the landing pages (home has an empty id, plus archive) aren't articles. + if (["404", "", "archive"].includes(starlightRoute.id)) { return; }