Adding Structured Data in Astro's Starlight Documentation Framework
Tags: comments, documentation, jsonld
Published: 2024-05-21 02:50:56

---
title: Adding Structured Data in Astro's Starlight Documentation Framework
published: true
date: 2024-05-21 02:46:35 UTC
tags: comments, documentation, jsonld
canonical_url: https://jamcomments.com/posts/structured-data-with-starlight
---

I remember when the Astro team first announced [Starlight](https://starlight.astro.build/?ref=cms.macarthur.me), their documentation framework. The timing was perfect. I had been meaning to overhaul the docs for [JamComments](https://jamcomments.com/?ref=cms.macarthur.me) and [TypeIt](https://www.typeitjs.com/?ref=cms.macarthur.me), but didn’t want to do so on the shoddy setups they were using at the time.

Since then, all of my side project documentation is built with Starlight, and I'm not moving away anytime soon. I‘d still call the project relatively new, but they’re iterating quickly, so I'm sure things are only going to get even better as time goes on.

At the time of writing, however, there’s one thing they don’t generate for you out of the box: structured data. But they _do_ allow you to [override individual UI components](https://starlight.astro.build/guides/overriding-components/?ref=cms.macarthur.me). That makes it relatively easy to roll some JSON-LD yourself.

## Overriding the Head Component

The only component we're interested in overriding is `<Head>`, which very shockingly renders the `<head>` of your HTML pages. In your Starlight configuration, add a `Head` property to `components` and point it to the new component we're about to build:

```
// astro.config.mjs

import { defineConfig } from "astro/config";
import starlight from "@astrojs/starlight";

export default defineConfig({
  // ... other configuration stuff.
  integrations: [
    starlight({
      components: {
        Head: "/src/components/docs/Head.astro",
      },
    }),
  ],
});
```

Next up, let's do some light scaffolding for the our new `<Head />`:

```
---
// our custom Head.astro

import Default from "@astrojs/starlight/components/Head.astro";

const { title, description } = Astro.props.entry.data;
---

<Default {...Astro.props} />

```

If you refresh your documentation locally, you'll notice that nothing's changed. That makes sense. All we're doing is rendering the same `Default` component it would've rendered anyway.

Quick note: most of the time, you'll want to include a `<slot />` as a child of the `<Default />` component to ensure any children passed in are rendered in the correct spot:

```
---
import Default from "@astrojs/starlight/WhateverComponent.astro";
---

<Default>
  <slot/>
</Default>
```

But the `<Head />` component [is a little different](https://github.com/withastro/starlight/blob/main/packages/starlight/components/Head.astro?ref=cms.macarthur.me). Instead of rendering a block of JSX, it's building an array of meta tags that's mapped to JSX. No `children` involved. So, at the time of writing, we're safe to leave the `<slot />` out.

## Building Structured Data

Next up, we can build our JSON-LD. We'll use the `schema-dts` for type safety and easier schema construction. You'll need to [choose the schema type](https://schema.org/?ref=cms.macarthur.me) most appropriate for your content, but I'll be using `TechArticle` here, which will be used as a generic for the `WithContext` type:

```
---
import type { Props } from "@astrojs/starlight/props";
import Default from "@astrojs/starlight/components/Head.astro";
import type { TechArticle, WithContext } from "schema-dts";
---

<Default {...Astro.props} />
```

It's now a matter of building some structured data, using the data provided by `Astro.props.entry.data` for page-specific information:

```
---
import type { Props } from "@astrojs/starlight/props";
import Default from "@astrojs/starlight/components/Head.astro";
import type { TechArticle, WithContext } from "schema-dts";

const { title, description } = Astro.props.entry.data;

const techArticleSchema: WithContext<TechArticle> = {
    "@context": "https://schema.org",
    "@type": "TechArticle",
    headline: title,
    description: description,
    url: Astro.url.href,
    author: {
      "@type": "Organization",
      name: "JamComments",
      url: "https://jamcomments.com",
      logo: {
        "@type": "ImageObject",
        url: "https://jamcomments.com/img/open-graph.jpg"
      }
  },
  image: {
    "@type": "ImageObject",
    url: "https://jamcomments.com/img/open-graph.jpg"
  }
};
---

<Default {...Astro.props} />
```

The last step, of course, is to stringify it into your HTML:

```
---
  // ...other stuff.
---

<Default {...Astro.props} />

<script 
    type="application/ld+json" 
    set:html={JSON.stringify(techArticleSchema)}>
</script>
```

If you refresh a documentation page now, you'll see it rendered in your HTML as expected:

![JSON-LD structured data rendered into the HTML](https://cms.macarthur.me/content/images/2024/05/image-3.png)

Of course, you're free to tweak anything you like, including the position of the markup relative to everything else in the `<head />`. In the spirit of not holding up anything critical to a user's experience, I prefer to stick it at the end. But do what you want. No one will die either way.

## Don't Forget to Validate It

After deploying it, you're not quite done. Verify that you actually just shipped some valid structured data.

There are two go-to resources for this: the [schema.org validator](https://validator.schema.org/?ref=cms.macarthur.me) and [Google's rich results test](https://search.google.com/test/rich-results?ref=cms.macarthur.me). Use both. You'd be surprised what one catches when the other says everything's fine.

## Check in w/ the Starlight Project

Like I said: the Astro team's been moving pretty quickly on Starlight ever since it's been around. So, it's very possible there will be a dedicated API for doing this sort of thing soon enough. So, keep tabs on their documentation as you're tinkering.