Autogenerated Starlight sidebar

Have you ever wanted to simplify sidebar generation in your Starlight project? Have you tried autogenerating the entire sidebar only to find it doesn't let you customise the structure to your needs?

This post demonstrates two Starlight features that make fully autogenerated sidebars flexible and reduce maintenance.

Use case

In medium to large Starlight projects, the problem arises that the manual creation and maintenance of the Starlight sidebar configuration is a tedious and time-consuming task.

In the past, Starlight didn't provide the right primitives for this, and the recommended approach was to move the autogeneration of the sidebar configuration one level deeper - so that only folders without nested subfolders were autogenerated. However, with the introduction of Route Data and the more recent generateId() hook of docsLoader, these scenarios can now be solved much more easily and efficiently.

Solving the ordering issue

A common problem always was: "How can I customise the order of sidebar folders if I autogenerate the sidebar?" Old issues like #1223 suggest a solution inspired by Nuxt Content where each segment of the whole route can be prefixed with numbers + dots to determine numeric ordering on a file-based level. An example for such a project could look like this:

src/content/docs/nested-folder/
├── 1.frameworks/
│ ├── 1.vue.md
│ └── 2.nuxt.md
└── 2.examples/
├── 1.vercel.md
└── 2.netlify.md

If you want to use this approach in Starlight, it doesn't work out-of-the-box, but with a simple setup you can make it work.

Starlight 0.35.0 introduced a callback function which you can pass to docsLoader() that can manipulate the id (URL) of the generated page. By splitting the entry by "/" and removing any number + dot on the segment level, you have the same functionality now in Starlight:

// src/content.config.ts
import { defineCollection } from "astro:content";
import { docsLoader } from "@astrojs/starlight/loaders";
import { docsSchema } from "@astrojs/starlight/schema";

const leadingNumberAndDotRegEx = /^\d+\./;
const fileExtensionRegEx = /\.(md|mdx)$/;

export const collections = {
docs: defineCollection({
loader: docsLoader({
// Remove file extension and
// leading numbers + dot from each segment
generateId: ({ entry }) =>
entry
.replace(fileExtensionRegEx, "")
.split("/")
.map((segment) => segment.replace(leadingNumberAndDotRegEx, ""))
.join("/"),
}),
schema: docsSchema(),
}),
};

The URL to access the pages now changes e.g., from /nested-folder/1.frameworks/1.vue to /nested-folder/frameworks/vue.

However, the folder names in the sidebar still include the 1. prefixes. So let's solve the sidebar manipulation as well:

Solving the sidebar folder naming issue

Starlight 0.32.0 introduced Route Data, which we can now utilize to manipulate folder names in the sidebar to

remove any leading number + dot from the folder name and

apply "Title Case" so they don't look completely lowercase1

// src/routeData.ts
import { defineRouteMiddleware } from "@astrojs/starlight/route-data";

const leadingNumberAndDotRegEx = /^\d+\./;
const wordSplitterRegEx = /\w\S*/g;

function toTitleCase(str: string) {
return str.replace(wordSplitterRegEx, (word) => {
return word.charAt(0).toUpperCase() + word.slice(1).toLowerCase();
});
}

function cleanGroupLabels(entries: any[]) {
for (const entry of entries) {
if (entry.type === "group") {
// Remove leading number + dot
let label = entry.label.replace(leadingNumberAndDotRegEx, "");
// Convert to Title Case
entry.label = toTitleCase(label);
// Recurse into children
cleanGroupLabels(entry.entries);
}
}
}

export const onRequest = defineRouteMiddleware((context) => {
const { sidebar } = context.locals.starlightRoute;
cleanGroupLabels(sidebar);
});

Don't forget to add the routeData.ts file to your astro.config.mjs:

// astro.config.mjs
starlight({
title: "Autogenerate whole sidebar",
sidebar: [
{
label: "Root Folder",
autogenerate: { directory: "nested-folder" },
},
],
routeMiddleware: "./src/routeData.ts",
}),

Of course you can adjust the code to your needs (e.g., remove the "Title Case" transformation because your file names are already written correctly).

Using HiDeoo's plugin

If you want to autogenerate your whole sidebar and still have the flexibility to customise everything you want, HiDeoo also made a dedicated plugin for such use cases: Starlight Auto Sidebar.

Especially those two features got mentioned in this article:

Controlling the order of individual directories

Adapting the displayed label of individual folders

However, if you want to automate the whole generation without having to specify labels and orders manually for each directory, coding the solution yourself (with some guidelines in this blog) is the preferred and recommended solution.

If you want to check out the code presented in this blog, feel free to visit the StackBlitz or the source code in the GitHub repo.

Note that internationalisation (i18n) of folder names is a missing feature with such a setup as there is currently nowhere to define translations of folder names, they just get converted into title case. Implementing logic to dynamically load translations could involve reading the locale field from the route data and define translations in json files. Otherwise, use HiDeoo's plugin, which supports i18n.