Getting Posts by Level in Astro + Starlight
In Astro + Starlight projects, articles are typically organized in a multi-level directory structure. Sometimes we need to retrieve posts from specific levels only. This article will introduce several methods to achieve this.
Getting Top-Level Posts
After retrieving posts using the getCollection API, we can determine their level by analyzing the slug
:
---import { getCollection } from 'astro:content';
// Get all postsconst allPosts = await getCollection('docs');
// Get top-level postsconst topLevelPosts = allPosts.filter(post => { // slug format example: "blog/astro" or "guide/start" // Count slashes to determine level return post.slug.split('/').length === 2;});---
<ul> {topLevelPosts.map(post => ( <li> <a href={`/docs/${post.slug}`}>{post.data.title}</a> </li> ))}</ul>
Getting Posts from a Specific Directory
If you want to get posts from a specific directory:
---import { getCollection } from 'astro:content';
// Get all postsconst allPosts = await getCollection('docs');
// Get posts from blog/astro directoryconst astroPosts = allPosts.filter(post => { return post.slug.startsWith('blog/astro/');});
// Get posts only from direct subdirectories of blog/astro (excluding deeper levels)const directAstroPosts = allPosts.filter(post => { const parts = post.slug.split('/'); return parts[0] === 'blog' && parts[1] === 'astro' && parts.length === 3;});---
Building a Directory Tree
If you need to build a complete directory tree structure:
---import { getCollection } from 'astro:content';
interface TreeNode { slug: string; title: string; children: Record<string, TreeNode>;}
// Build directory treefunction buildTree(posts: any[]) { const tree: Record<string, TreeNode> = {};
posts.forEach(post => { const parts = post.slug.split('/'); let current = tree;
parts.forEach((part, index) => { if (!current[part]) { current[part] = { slug: parts.slice(0, index + 1).join('/'), title: index === parts.length - 1 ? post.data.title : part, children: {} }; } current = current[part].children; }); });
return tree;}
const allPosts = await getCollection('docs');const directoryTree = buildTree(allPosts);---
<!-- Recursively render directory tree --><ul> {Object.values(directoryTree).map(node => ( <li> <a href={`/docs/${node.slug}`}>{node.title}</a> {Object.keys(node.children).length > 0 && ( <ul> {Object.values(node.children).map(child => ( <li> <a href={`/docs/${child.slug}`}>{child.title}</a> </li> ))} </ul> )} </li> ))}</ul>
Grouping by Level
If you need to group posts by level:
---import { getCollection } from 'astro:content';
// Get all postsconst allPosts = await getCollection('docs');
// Group by levelconst postsByLevel = allPosts.reduce((acc, post) => { const level = post.slug.split('/').length - 1; if (!acc[level]) { acc[level] = []; } acc[level].push(post); return acc;}, {} as Record<number, typeof allPosts>);
// postsByLevel[1] contains all first-level posts// postsByLevel[2] contains all second-level posts// And so on---
Utility Functions
Here are some useful utility functions for handling post levels:
// Get post levelfunction getPostLevel(slug: string): number { return slug.split('/').length - 1;}
// Get parent directory pathfunction getParentPath(slug: string): string { const parts = slug.split('/'); return parts.slice(0, -1).join('/');}
// Check if it's a subdirectoryfunction isChildOf(childSlug: string, parentSlug: string): boolean { return childSlug.startsWith(parentSlug + '/');}
// Get all posts at a specific levelfunction getPostsByLevel(posts: any[], level: number) { return posts.filter((post) => getPostLevel(post.slug) === level);}
Important Considerations
-
Performance: When dealing with many posts, consider caching the results to avoid repeated calculations.
-
Path Standardization: Ensure consistent slug path formats for easier level determination.
-
Empty Directory Handling: Some directories might have no direct posts, only subdirectories - handle these cases appropriately.
-
Sorting: Posts at the same level might need to be sorted according to specific rules, which can be done using the posts’ frontmatter data.
Conclusion
Through proper use of slug analysis and filtering, we can flexibly retrieve and organize posts from different levels. This is particularly helpful for building navigation menus, breadcrumb navigation, and other features. Depending on your specific needs, you can combine these methods to implement more complex post organization and display functionality.