Astro: Add Related Posts to Blog Entries

This article covers adding a “Related Posts” feature to an Astro blog. Updating the content collection schema to include a new property and creating a custom component links relevant blog posts within each entry.

Unlike the previous/next post navigation, which surfaces links based on chronological adjacency, related posts are curated manually through frontmatter.

Manual curation trades scale for precision. An automatic approach, such as matching posts by shared tags or category, requires no per-post upkeep but can surface weak or tangential matches. Listing related posts explicitly in frontmatter takes more maintenance as a website grows, but guarantees every link is intentional.

The blog content collection schema in content.config.ts is updated to include a relatedPosts property as an optional array of strings.

relatedPosts: z.array(z.string()).optional()

Now, each collection entry’s frontmatter can use the relatedPosts property to link related posts by their id or slug.

import { glob } from 'astro/loaders';
import { defineCollection, z } from 'astro:content';

const blog = defineCollection({
  loader: glob({ base: './src/content/blog', pattern: '**/*.{md,mdx}' }),
  schema: z.object({
    title: z.string(),
    pubDate: z.coerce.date(),
    relatedPosts: z.array(z.string()).optional(),
  }),
});

export const collections = { blog };

To display links to related posts in an entry’s frontmatter, a new component called BlogPostRelatedPosts.astro is created.

AstroProject/
└── src/
    └── components/
        └── BlogPostRelatedPosts.astro

This component retrieves the blog collection and applies a filter with two conditions:

  1. The current post is excluded to avoid linking back to itself.
post.id !== Astro.params.slug
  1. Only collection entries listed in the current entry’s relatedPosts frontmatter are included; all others are excluded.
relatedPosts?.includes(post.id)

After applying the filters, the component displays a Related Posts heading with links and titles for each related post.

---
import { getCollection } from 'astro:content';

const { relatedPosts } = Astro.props;

const posts = (await getCollection('blog'))
  .filter(post =>
    post.id !== Astro.params.slug &&
    relatedPosts?.includes(post.id)
  )
  .sort((a, b) => (b.data.pubDate?.valueOf() ?? 0) - (a.data.pubDate?.valueOf() ?? 0));
---

{
  (posts.length > 0) && (
    <aside>
      <h2>Related Posts</h2>
      {
        posts.map((post) => (
          <a href={`/blog/${post.id}/`}>{post.data.title}</a>
        ))
      }
    </aside>
  )
}

Add Component to Post Layout

To use the component, import BlogPostRelatedPosts into the blog layout file. In this example, <BlogPostRelatedPosts /> is placed after the main content to display the related post links before the footer.

---
import BlogPostRelatedPosts from '../components/BlogPostRelatedPosts.astro';

const { title, relatedPosts } = Astro.props;
---

<!doctype html>
<html lang="en">
  <head>
  </head>
  <body>
    <main>
      <article>
        <h1>{title}</h1>
     </article>
    </main>
    <BlogPostRelatedPosts relatedPosts={relatedPosts} />
  </body>
</html>

Adjust Dynamic Route

The dynamic route for displaying collection entries exports a getStaticPaths() that returns an array of objects with a params property, including slug as post.id.

params: { slug: post.id }

In this example, [...slug].astro is updated as follows.

---
import BlogPost from '../../layouts/StandardPostLayout.astro';

import { getCollection } from 'astro:content';
import { render } from 'astro:content';

export async function getStaticPaths() {
  const posts = (await getCollection('blog'))
    .sort((a, b) => (b.data.pubDate?.valueOf() ?? 0) - (a.data.pubDate?.valueOf() ?? 0));

  return posts.map((post) => ({
    params: { slug: post.id },
    props: post,
  }));
}

const post = Astro.props;
const { Content } = await render(post);
---

<BlogPost {...post.data}>
  <Content />
</BlogPost>

With the code changes complete, the relatedPosts property can be used in the frontmatter by adding id references to related posts in the array.

---
title: 'Example Draft Post'
pubDate: 'Mar 10 2026'
relatedPosts: [
  'related-slug-1',
  'related-slug-2'
]
---

Results

The “Related Posts” section at the bottom of this article demonstrates the feature in use.

Summary

Adding a related posts property to the content collection schema and creating a custom component displays related posts within an Astro blog. This approach enhances user engagement by linking relevant content and ensuring a dynamic and organized display of related articles.