John Dalesandro

Astro: Add Related Posts to Blog Entries

In this guide, I’ll walk through the process of adding a “Related Posts” feature to your Astro blog. By updating the content collection schema to include a new property and creating a custom component, you can easily link relevant blog posts within each entry.

Instructions

Let’s start by updating the blog content collection schema in content.config.ts 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, we create a new component called BlogPostRelatedPosts.astro.

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

This component retrieves the blog collection and applies two filters:

  1. If the relatedPosts property includes a reference to its own slug, that link is excluded to avoid linking back to itself.
.filter((post) => post.id != Astro.params.slug)
  1. Only collection entries listed in the current entry’s relatedPosts frontmatter are included; all others are excluded.
.filter((post) => relatedPosts && 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)
  .filter((post) => relatedPosts && relatedPosts.includes(post.id))
  .sort((a, b) => b.data.pubDate.valueOf() - a.data.pubDate.valueOf());
---

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

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

Make sure 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() - a.data.pubDate.valueOf());

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

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

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

Now that the code changes are complete, you can use the relatedPosts property in the frontmatter by adding id references to related posts in the array.

---
title: ''
pubDate: ''
relatedPosts: ['related-slug-1','related-slug-2']
---

Results

You can see how Related Posts works at the bottom of this post.

Summary

By adding a related posts property to the content collection schema and creating a custom component, you can easily display related posts within your Astro blog. This approach enhances user engagement by linking relevant content and ensuring a dynamic and organized display of related articles.