Astroブログでタグページと関連記事を作るときの考え方

Astroで記事が増えてくると、タグ別ページと関連記事が欲しくなります。

どちらも、Markdownのfrontmatterに tags を持たせておくと作りやすいです。

frontmatterにtagsを持たせる

---
title: "記事タイトル"
pubDate: 2026-06-28
tags: ["Astro", "Markdown"]
---

Content Collectionsのschema側でも配列として定義しておきます。

tags: z.array(z.string()).default([])

schemaの設定は AstroのContent Collectionsでfrontmatterを型チェックする設定 に分けています。

タグ一覧を作る

記事一覧からタグを集計します。

const counts = new Map<string, number>();

for (const post of posts) {
  for (const tag of post.data.tags) {
    counts.set(tag, (counts.get(tag) ?? 0) + 1);
  }
}

タグ名をURLにする場合は、slug化しておきます。

function getTagSlug(tag: string) {
  return tag.toLowerCase().replace(/[^a-z0-9]+/g, '-');
}

C++ のようなタグは、そのままだとURLにしづらいので c-plus-plus のように変換すると扱いやすいです。

タグ別ページを作る

src/pages/tags/[tag].astro を作り、getStaticPaths() でタグごとのページを生成します。

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

export async function getStaticPaths() {
  const posts = await getCollection('posts', ({ data }) => !data.draft);
  const tags = getTagSummaries(posts);

  return tags.map((tag) => ({
    params: { tag: tag.slug },
    props: {
      tag: tag.name,
      posts: posts.filter((post) => post.data.tags.includes(tag.name)),
    },
  }));
}
---

動的ルーティングはAstro公式のRoutingにまとまっています。

https://docs.astro.build/en/guides/routing/#dynamic-routes

関連記事を出す

関連記事は、共有タグ数で並べるのが簡単です。

const relatedPosts = allPosts
  .filter((candidate) => candidate.id !== post.id)
  .map((candidate) => ({
    post: candidate,
    score: candidate.data.tags.filter((tag) => post.data.tags.includes(tag)).length,
  }))
  .filter(({ score }) => score > 0)
  .sort((a, b) => b.score - a.score)
  .slice(0, 3)
  .map(({ post }) => post);

完璧な推薦ではありませんが、技術ブログならまずこれで十分です。

まとめ

タグページと関連記事は、同じ tags から作れます。

  • frontmatterに tags を持たせる
  • タグ名はURL用にslug化する
  • /tags/{tag}/getStaticPaths() で生成する
  • 関連記事は共有タグ数で並べる

記事が増える前にタグの粒度を決めておくと、後から整理しやすくなります。

関連記事

参考