Astroでカテゴリごとのページを作る方法
2023/06/11
Astroでブログや記事をカテゴリ分けして、カテゴリごとの一覧ページを作る方法を紹介します。
記事やブログのフロントマターにカテゴリを定義する
カテゴリは記事やブログごとに定義するものですので、各記事のフロントマターで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>