Automatic Generation of Post Lists in Starlight
I’m using Starlight + the Starlight-blog plugin for my blog. My blog has a page for short notes and two additional sections that contain groups of notes: a reading journal and development guides.
For the reading journal, I wanted to create a table of contents page, gathering the titles and links to all entries in one place. Adding each one manually each time I write a new book entry would be tedious, so I decided to automate the list generation.
Querying Collections in Astro
Astro allows querying collections, which seemed like a great solution. I needed to query the collection of entries in content/docs/content_list/reading and display a list of all these entries on one page.
Sounds perfect, except: Astro only works with collections at the top level.
A content collection is any top-level directory inside the reserved src/content project directory, such as src/content/newsletter and src/content/authors. (documentation)
However, Starlight has already created a top-level collection - docs
, where I organize my collections.
So, let’s proceed with filtering to retrieve only the needed entries.
Filtering Collections in Starlight
In getCollection()
, you can pass a callback with a filtering function. To simplify filtering,
I’ll assign a category to each note - basically, a section or topic for the entry.
Let’s add an optional category property to the schema.
import { defineCollection, z } from 'astro:content';import { docsSchema, i18nSchema } from '@astrojs/starlight/schema';import { blogSchema } from 'starlight-blog/schema';
const customFields = z.object({ // for quering entries in collections category: z.string().optional(),})export const collections = { docs: defineCollection({ schema: docsSchema({ extend: (context) => { const baseSchema = blogSchema(context); return baseSchema.merge(customFields); } }) }), i18n: defineCollection({ type: 'data', schema: i18nSchema({}), }) })};
Next, we create a component that retrieves all entries in the docs collection and filters only those
marked with category: reading
.
---const books = await getCollection('docs', (entry) => { return entry.data.category === 'reading';});---
<ul> {books.map(book => <li><a href={ `/blog/${book.slug}`}>{book.data.title}</a></li>)}</ul>
In each reading journal entry, add category: reading
.
---title: 'Title of book'category: 'reading'---
Great, it works!
What About Localized Versions?
Let’s switch to the Russian version… and notice some issues. We need to avoid duplicating entries but still display the correct ones for each language.
What’s next? We customize our list component and refine the category system to handle content versions in different languages (noting as a to-do that we’ll eventually switch from simple strings to a finite list of categories to keep things organized and consistent).
Now, our component to collect and display notes by category looks like this:
type Props = { category: string;}
const {category} = Astro.props;
const items = await getCollection('docs', (entry) => { return entry.data.category === category;});---<ul> {items.map(item => <li><a href={ `/blog/${item.slug}`}>{item.data.title}</a></li>)}</ul>
In files for each language, we specify the relevant category:
---title: 'Title of book'category: 'reading-en'---
---title: 'Название книги'category: 'reading-ru'---
Done! You can see the result on the book list page.