ぬまろぐ

←戻る

Astroでカテゴリごとのページを作る方法

2023/06/11

Astroでブログや記事をカテゴリ分けして、カテゴリごとの一覧ページを作る方法を紹介します。

IMG

記事やブログのフロントマターにカテゴリを定義する

カテゴリは記事やブログごとに定義するものですので、各記事のフロントマターでcategoriesという属性を追加します。(名前はなんでもよいです)

---
layout: ../../layouts/PostLayout.astro
title: タイトル
author: 作者
date: 2023/06/11
categories: [カテゴリ1, カテゴリ2]
eyecatch: /hp/posts/20230603_1/eyecatch.jpg
description: 概要文
---

本文

カテゴリ毎のページを作成する

ページリンクと同じ要領ですが、「article-[category]-[page].astro」のような名前のページを用意します。ビルドをすると[category]というが実際のカテゴリ毎のファイル名にになって出力される。

article-[category]-[page].astroの中身でgetStaticPathsの中でカテゴリ一覧を抽出し、カテゴリとページサイズをpaginateにまとめたリストを返却します。

---
import Layout from "../layouts/Layout.astro";
import Paging from "../components/Paging.astro";
import Category from "../components/Category.astro";
export async function getStaticPaths({ paginate }) {
	const allPosts = await Astro.glob("../pages/articles/*.md");
	// 記事を日付でソート
	allPosts.sort(
		(a, b) =>
			Date.parse(b.frontmatter.date) - Date.parse(a.frontmatter.date)
	);
	// カテゴリを一覧を抽出
	const categories = []
	allPosts.forEach((p) => {
		const cats: string[] = p.frontmatter.categories ?? [];
		cats.forEach((c) => {
			if (!categories.includes(c)) {
				categories.push(c);
			}
		});
	});
  // カテゴリとページサイズの一覧を返却する
	return categories.map((category) => {
		const filteredPosts = allPosts.filter(
			(post) => post.frontmatter.categories.includes(category)
		);
		return paginate(filteredPosts, {
			params: { category },
			pageSize: 12,
		});
	});
}
const { page } = Astro.props;
const params = Astro.params;
---

<Layout title="ぬまろぐ">
	<Category selectedCategory={params.category}/>
	<ul role="list" class="blog-card-grid">
		{
			page.data.map((post) => (
				<a class="blog-card" href={post.url}>
					<article>
							<img
								class="eyecatch"
								alt=""
								src={post.frontmatter.eyecatch}
							/>
						<div />
						<h2>{post.frontmatter.title}</h2>
						<p>{post.frontmatter.date}</p>
						<p>{post.frontmatter.categories.join(",")}</p>
						<p>{post.frontmatter.description.slice(0, 35)}...</p>
					</article>
				</a>
			))
		}
	</ul>
	<p>
		<Paging page={page} name="article" category={params.category} />
	</p>
</Layout>

カテゴリ一覧を表示する

カテゴリ一覧を表示しないと、カテゴリを選択できないので、リンク付き一覧を表示するコンポーネントも作ってみました。

これを記事一覧ページに埋め込みます。

---
export interface Props {
  selectedCategory: String;
}
// カテゴリを一覧を抽出
const allPosts = await Astro.glob("../pages/articles/*.md");
const categories = [];
allPosts.forEach((p) => {
  const cats: string[] = p.frontmatter.categories ?? [];
  cats.forEach((c) => {
    if (!categories.includes(c)) {
      categories.push(c);
    }
  });
});

const { selectedCategory } = Astro.props;
---

<div class="container">
  <div class="category-area">
    <div class="title">タグ:</div>
    {
      categories.map((category) => (
        selectedCategory == category ? (
          <a
            href={import.meta.env.BASE_URL + "article-" + category + "-1/"}
            class="category selected"
          >
            {category}
          </a>
        ) : (
          <a
            href={import.meta.env.BASE_URL + "article-" + category + "-1/"}
            class="category"
          >
            {category}
          </a>
        )
      ))
    }
  </div>
</div>

<style>
  .title {
    font-size: 0.9em;
    /* font-weight: bold; */
    margin: 2px;
  }
  .container {
    background: #fff;
    border-radius: 0.5rem;
    background-position: 100%;
    transition: background-position 0.6s cubic-bezier(0.22, 1, 0.36, 1);
    box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1),
      0 2px 4px -2px rgba(0, 0, 0, 0.1);
  }
  .category-area {
    display: flex;
    flex-wrap: wrap;
  }
  .category {
    background: #fff;
    border-color: #aaa;
    border-style: solid;
    border-width: 1px;
    border-radius: 9999px;
    margin: 3px;
    padding: 3px;
    text-decoration: none;
    color: inherit;
    font-size: 0.5em;
  }

  .selected {
    background: #9ca5ce;
  }
</style>